contract CryptosparkXMULTIPLE is BEP1155, Ownable, BEP1155Burnable {
    event Approve(
        address indexed owner,
        uint256 indexed token_id,
        bool approved
    );
    event OrderPlace(
        address indexed from,
        uint256 indexed tokenId,
        uint256 indexed value
    );
    event CancelOrder(address indexed from, uint256 indexed tokenId);
    event ChangePrice(
        address indexed from,
        uint256 indexed tokenId,
        uint256 indexed value
    );

    using SafeMath for uint256;

    struct Order {
        uint256 tokenId;
        uint256 price;
    }

    mapping(address => mapping(uint256 => Order)) public order_place;
    mapping(uint256 => mapping(address => bool)) public checkOrder;
    mapping(uint256 => uint256) public totalQuantity;

    mapping(uint256 => uint256) public _royal;
    mapping(string => address) private tokentype;

    uint256 private serviceValue;

    string private _currentBaseURI;
    uint256 public _tid;

    // string public name;
    // string public symbol;

    constructor(
        uint256 id,
        uint256 _serviceValue,
        string memory _name,
        string memory _symbol
    ) BEP1155("", _name, _symbol) {
        _tid = id;
        serviceValue = _serviceValue;
    }

    function getServiceFee() public view returns (uint256) {
        return serviceValue;
    }

    function serviceFunction(uint256 _serviceValue) public onlyOwner {
        serviceValue = _serviceValue;
    }

    function getTokenAddress(string memory _type)
        public
        view
        returns (address)
    {
        return tokentype[_type];
    }

    function addTokenType(string memory _type, address tokenAddress)
        public
        onlyOwner
    {
        tokentype[_type] = tokenAddress;
    }


    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _creator[tokenId] != address(0);
    }

    function TokenExist(uint256 tokenId) public view returns (bool) {
        return !_exists(tokenId);
    }

    function mint(
        uint256 id,
        uint256 value,
        uint256 royal,
        uint256 nooftoken,
        string memory ipfsname,
        string memory ipfsimage,
        string memory ipfsmetadata,
        address from,
        uint256 nonce,
        bytes memory signature,
        address operator
    ) public {
        if (!_exists(id)) {
            bytes32 message = prefixed(
                keccak256(abi.encodePacked(from, nonce))
            );

            require(
                recoverSigner(message, signature) == from,
                "wrong signature"
            );

            setApprovalForAll(operator, true);

            _mint(from, id, nooftoken, "");
            token_id[id] = Metadata(ipfsname, ipfsimage, ipfsmetadata);
            _creator[id] = from;
            _royal[id] = royal.mul(1e18);
            if (value != 0) {
                orderPlace(from, id, value);
            }
        }
    }

    function mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts
    ) public {
        _mintBatch(to, ids, amounts, "");
    }

    function saleWithToken(
        address payable from,
        uint256 tokenId,
        uint256 amount,
        uint256 nooftoken,
        string memory bidtoken
    ) public {
        require(
            amount == order_place[from][tokenId].price.mul(nooftoken),
            "Insufficent Balance"
        );
        _saleToken(from, tokenId, amount, bidtoken);
        _setApprovalForAll(from, msg.sender, true);
        saleTokenTransfer(from, tokenId, nooftoken);
    }

    // BNB TRANSFER PURCHASE
    function saleToken(
        address payable from,
        uint256 tokenId,
        uint256 amount,
        uint256 nooftoken
    ) public payable {
        require(
            amount == order_place[from][tokenId].price.mul(nooftoken),
            "Invalid Balance"
        );
        _saleToken(from, tokenId, amount, "BNB");
        _setApprovalForAll(from, msg.sender, true);
        saleTokenTransfer(from, tokenId, nooftoken);
    }

    function _saleToken(
        address payable from,
        uint256 tokenId,
        uint256 amount,
        string memory bidtoken
    ) internal {
        uint256 val = pBEPent(amount, serviceValue).add(amount);
        if (
            keccak256(abi.encodePacked((bidtoken))) ==
            keccak256(abi.encodePacked(("BNB")))
        ) {
            require(msg.value == val, "Insufficient Balance");
            address payable create = payable(_creator[tokenId]);
            (uint256 _adminfee, uint256 roy, uint256 netamount) = calc(
                amount,
                _royal[tokenId],
                serviceValue
            );
            require(
                msg.value == _adminfee.add(roy.add(netamount)),
                "Insufficient Balance"
            );
            address payable admin = payable(owner());
            admin.transfer(_adminfee);
            create.transfer(roy);
            from.transfer(netamount);
        } else {
            BEP20 t = BEP20(tokentype[bidtoken]);
            uint256 approveValue = t.allowance(msg.sender, address(this));
            require(approveValue >= val, "Insufficient Balance");
            (uint256 _adminfee, uint256 roy, uint256 netamount) = calc(
                amount,
                _royal[tokenId],
                serviceValue
            );
            require(
                approveValue >= _adminfee.add(roy.add(netamount)),
                "Insufficient Balance"
            );
            t.transferFrom(msg.sender, owner(), _adminfee);
            t.transferFrom(msg.sender, _creator[tokenId], roy);
            t.transferFrom(msg.sender, from, netamount);
        }
    }

    function pBEPent(uint256 value1, uint256 value2)
        internal
        pure
        returns (uint256)
    {
        uint256 result = value1.mul(value2).div(1e20);
        return (result);
    }

    function calc(
        uint256 amount,
        uint256 royal,
        uint256 _serviceValue
    )
        internal
        pure
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        uint256 fee = pBEPent(amount, _serviceValue);
        uint256 roy = pBEPent(amount, royal);
        uint256 netamount = amount.sub(fee.add(roy));
        fee = fee.add(fee);
        return (fee, roy, netamount);
    }

    function saleTokenTransfer(
        address payable from,
        uint256 tokenId,
        uint256 NOFToken
    ) internal {
        safeTransferFrom(from, msg.sender, tokenId, NOFToken, "");
    }

    function dumTokenTransfer(
        address payable from,
        uint256 tokenId,
        uint256 NOFToken
    ) public {
        safeTransferFrom(from, msg.sender, tokenId, NOFToken, "");
    }

    function acceptBId(
        string memory bidtoken,
        address bidaddr,
        uint256 amount,
        uint256 tokenId,
        uint256 NOFToken
    ) public {
        _acceptBId(bidtoken, bidaddr, amount, tokenId, owner());
        tokenTrans(tokenId, msg.sender, bidaddr, NOFToken);
        if (checkOrder[tokenId][msg.sender] == true) {
            if (_balances[tokenId][msg.sender] == 0) {
                delete order_place[msg.sender][tokenId];
                checkOrder[tokenId][msg.sender] = false;
            }
        }
    }

    function _acceptBId(
        string memory tokenAss,
        address from,
        uint256 amount,
        uint256 tokenId,
        address admin
    ) internal {
        uint256 val = pBEPent(amount, serviceValue).add(amount);
        BEP20 t = BEP20(tokentype[tokenAss]);
        uint256 approveValue = t.allowance(from, address(this));
        require(approveValue >= val, "Insufficient Balance");
        require(_balances[tokenId][msg.sender] > 0, "Is Not a Owner");
        (uint256 _adminfee, uint256 roy, uint256 netamount) = calc(
            amount,
            _royal[tokenId],
            serviceValue
        );
        require(
            approveValue >= _adminfee.add(roy.add(netamount)),
            "Insufficient Balance"
        );
        t.transferFrom(from, admin, _adminfee);
        t.transferFrom(from, _creator[tokenId], roy);
        t.transferFrom(from, msg.sender, netamount);
    }

    function tokenTrans(
        uint256 tokenId,
        address from,
        address to,
        uint256 NOFToken
    ) internal {
        safeTransferFrom(from, to, tokenId, NOFToken, "");
    }

    function orderPlace(
        address from,
        uint256 tokenId,
        uint256 _price
    ) public {
        require(_balances[tokenId][from] > 0, "Is Not a Owner");
        Order memory order;
        order.tokenId = tokenId;
        order.price = _price;
        order_place[from][tokenId] = order;
        checkOrder[tokenId][from] = true;
        emit OrderPlace(from, tokenId, _price);
    }

    function cancelOrder(uint256 tokenId) public {
        require(_balances[tokenId][msg.sender] > 0, "Is Not a Owner");
        delete order_place[msg.sender][tokenId];
        checkOrder[tokenId][msg.sender] = false;
        emit CancelOrder(msg.sender, tokenId);
    }

    function changePrice(uint256 value, uint256 tokenId) public {
        require(_balances[tokenId][msg.sender] > 0, "Is Not a Owner");
        require(value < order_place[msg.sender][tokenId].price);
        order_place[msg.sender][tokenId].price = value;
        emit ChangePrice(msg.sender, tokenId, value);
    }

    function burnToken(
        address from,
        uint256 tokenId,
        uint256 NOFToken
    ) public {
        require(
            (_balances[tokenId][msg.sender] >= NOFToken &&
                from == msg.sender) || msg.sender == owner(),
            "Your Not a Token Owner or insufficient Token Balance"
        );
        require(
            _balances[tokenId][from] >= NOFToken,
            "Your Not a Token Owner or insufficient Token Balance"
        );
        burn(from, tokenId, NOFToken);
        if (_balances[tokenId][from] == NOFToken) {
            if (checkOrder[tokenId][from] == true) {
                delete order_place[from][tokenId];
                checkOrder[tokenId][from] = false;
            }
        }
    }

    function prefixed(bytes32 hash) internal pure returns (bytes32) {
        return
            keccak256(
                abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
            );
    }

    function recoverSigner(bytes32 message, bytes memory sig)
        internal
        pure
        returns (address)
    {
        uint8 v;
        bytes32 r;
        bytes32 s;

        (v, r, s) = splitSignature(sig);

        return ecrecover(message, v, r, s);
    }

    function splitSignature(bytes memory sig)
        internal
        pure
        returns (
            uint8,
            bytes32,
            bytes32
        )
    {
        require(sig.length == 65);

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        return (v, r, s);
    }
}
function saleToken(
    address payable from,
    uint256 tokenId,
    uint256 amount,
    uint256 nooftoken
) public payable {
    require(
        amount == order_place[from][tokenId].price.mul(nooftoken),
        "Invalid Balance"
    );
    _saleToken(from, tokenId, amount, "BNB");
    _setApprovalForAll(from, msg.sender, true);
    saleTokenTransfer(from, tokenId, nooftoken);
}

// BNB TRANSFER PURCHASE