/// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./interfaces/IMarketplace.sol";
import "./WithTreasury.sol";

contract Marketplace is IMarketplace, WithTreasury, ERC1155Holder, AccessControl, ReentrancyGuard {
    bytes32 public constant SELLABLE_ROLE = keccak256("SELLABLE_ROLE");
    bytes32 public constant SELLER_ROLE = keccak256("SELLER_ROLE");

    /// @dev seller => Sales
    mapping(address => Sale[]) private _sales;

    constructor(address payable modaTreasury_, uint256 treasuryFee_) WithTreasury(modaTreasury_, treasuryFee_) {
        require(treasuryFee_ > 0, "Treasury fee cannot be 0");

        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(SELLER_ROLE, msg.sender);
    }

    function createSale(
        address tokenHolder,
        address payable beneficiary,
        address token,
        uint256 tokenId,
        uint256 tokenAmount,
        uint256 pricePerToken,
        uint256 startAt,
        uint256 endAt,
        uint256 maxCountPerWallet
    ) external override onlyRole(SELLER_ROLE) {
        require(hasRole(SELLABLE_ROLE, token), "Unsupported token");
        require(address(0) != beneficiary, "Beneficiary cannot be 0x0");
        require(tokenAmount > 0, "Token amount cannot be 0");
        require(startAt < endAt, "Start must be before end");
        require(startAt >= block.timestamp, "Must start in the future");
        require(maxCountPerWallet > 0, "Max wallet count must be gt 0");

        IERC1155(token).safeTransferFrom(tokenHolder, address(this), tokenId, tokenAmount, "");

        _sales[msg.sender].push(
            Sale({
                tokenHolder: tokenHolder,
                beneficiary: beneficiary,
                token: token,
                tokenId: tokenId,
                tokenAmount: tokenAmount,
                pricePerToken: pricePerToken,
                startAt: startAt,
                endAt: endAt,
                maxCountPerWallet: maxCountPerWallet
            })
        );

        emit SaleCreated(msg.sender, _sales[msg.sender].length - 1);
    }

    function purchase(
        address seller,
        uint256 saleId,
        uint256 tokenAmount
    ) external payable override nonReentrant {
        require(tokenAmount > 0, "Cannot mint 0 tokens");
        Sale storage sale = _sales[seller][saleId];
        require(tokenAmount <= sale.tokenAmount, "Insufficient supply");

        if (seller == _msgSender()) {
            sale.tokenAmount -= tokenAmount;
            IERC1155(sale.token).safeTransferFrom(address(this), _msgSender(), sale.tokenId, tokenAmount, "");
            emit Purchase(sale.token, sale.tokenId, _msgSender(), seller, saleId, tokenAmount, block.timestamp);
            return;
        }

        require(sale.startAt <= block.timestamp, "Sale has not started yet");
        require(sale.endAt > block.timestamp, "Sale has ended");
        require(msg.value == sale.pricePerToken * tokenAmount, "Wrong payment amount");

        require(
            (IERC1155(sale.token).balanceOf(msg.sender, sale.tokenId) + tokenAmount) <= sale.maxCountPerWallet,
            "Cannot exceed max count per wallet"
        );

        uint256 totalPrice = sale.pricePerToken * tokenAmount;
        uint256 fee = (treasuryFee * totalPrice) / 10_000;
        modaTreasury.transfer(fee);
        sale.beneficiary.transfer(totalPrice - fee);

        sale.tokenAmount -= tokenAmount;

        IERC1155(sale.token).safeTransferFrom(address(this), _msgSender(), sale.tokenId, tokenAmount, "");

        emit Purchase(sale.token, sale.tokenId, _msgSender(), seller, saleId, tokenAmount, block.timestamp);
    }

    function saleAt(address seller, uint256 index) external view override returns (Sale memory) {
        return _sales[seller][index];
    }

    function saleCount(address seller) external view override returns (uint256) {
        return _sales[seller].length;
    }

    function setTreasuryFee(uint256 newFee_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _setTreasuryFee(newFee_);
    }

    function setTreasury(address payable newTreasury) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _setTreasury(newTreasury);
    }

    /// @inheritdoc	ERC165
    function supportsInterface(bytes4 interfaceId) public view override(AccessControl, ERC1155Receiver) returns (bool) {
        return super.supportsInterface(interfaceId);
    }
}
[{"inputs":[{"internalType":"address payable","name":"modaTreasury_","type":"address"},{"internalType":"uint256","name":"treasuryFee_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"uint256","name":"saleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"Purchase","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":true,"internalType":"uint256","name":"saleId","type":"uint256"}],"name":"SaleCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldTreasury","type":"address"},{"indexed":true,"internalType":"address","name":"newTreasury","type":"address"}],"name":"Treasury","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"TreasuryFeeSet","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SELLABLE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SELLER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenHolder","type":"address"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint256","name":"pricePerToken","type":"uint256"},{"internalType":"uint256","name":"startAt","type":"uint256"},{"internalType":"uint256","name":"endAt","type":"uint256"},{"internalType":"uint256","name":"maxCountPerWallet","type":"uint256"}],"name":"createSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"modaTreasury","outputs":[{"internalType":"address payable","name":"","type":"address"}],"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":"seller","type":"address"},{"internalType":"uint256","name":"saleId","type":"uint256"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"purchase","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"seller","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"saleAt","outputs":[{"components":[{"internalType":"address","name":"tokenHolder","type":"address"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint256","name":"pricePerToken","type":"uint256"},{"internalType":"uint256","name":"startAt","type":"uint256"},{"internalType":"uint256","name":"endAt","type":"uint256"},{"internalType":"uint256","name":"maxCountPerWallet","type":"uint256"}],"internalType":"struct IMarketplace.Sale","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"seller","type":"address"}],"name":"saleCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"newTreasury","type":"address"}],"name":"setTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFee_","type":"uint256"}],"name":"setTreasuryFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasuryFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]