// Contract: XDSeaTrade
// Target function: function bulkBuy(
21 {
    function mint(
        string memory ipfsmetadata,
        address to,
        uint256 royal,
        uint256 id_
    ) external;

    function getCreatorsAndRoyalty(
        uint256 tokenid
    ) external view returns (address, uint256);
}

interface XDSea1155 {
    function mint(
        string memory ipfsmetadata,
        address to,
        uint total,
        uint256 royal,
        uint256 id_
    ) external;

    function getCreatorsAndRoyalty(
        uint256 tokenid
    ) external view returns (address, uint256);

    function TransferNFT(address to, uint256 tokenid, uint256 count) external;
}

contract XDSeaTrade is Initializable, OwnableUpgradeable {
    event OrderPlace(
        address indexed from,
        uint256 indexed tokenId,
        uint256 indexed value
    );
    event CancelOrder(address indexed from, uint256 indexed tokenId);
    event ColletionId(uint256 indexed collectionId, uint256 indexed cRoyalty);
    event ChangePrice(
        address indexed from,
        uint256 indexed tokenId,
        uint256 indexed value
    );
    event Create(address indexed _to, uint256 indexed tokenId, string status);
    using SafeMathUpgradeable for uint256;

    function initialize() public initializer {
        __Ownable_init();
        serviceValue = 2000000000000000000;
        sellervalue = 2000000000000000000;
        deci = 18;
        publicMint = true;
        _tid = 1;
    }

    struct Order {
        uint256 tokenId;
        uint256 price;
        address contractAddress;
        bool listOrNot;
    }
    mapping(address => mapping(uint256 => Order)) public order_place;
    mapping(string => address) private tokentype;
    mapping(address => mapping(address => bool)) public approveStatus;
    struct collectionRoyalty {
        uint256 tokenId;
        uint256 colletionRoyalty;
        address[] spliters;
        uint256[] splitpercentage;
    }
    uint256 private serviceValue;
    uint256 private sellervalue;
    bool public publicMint;
    address public xdsea721;
    address public xdsea1155;
    uint256 deci;
    uint256 public _tid;

    function getApproveStatus(
        address owneraddrrss,
        address contractaddress
    ) public view returns (bool) {
        return approveStatus[owneraddrrss][contractaddress];
    }

    function getServiceFee() public view returns (uint256, uint256) {
        return (serviceValue, sellervalue);
    }

    function setServiceValue(
        uint256 _serviceValue,
        uint256 sellerfee
    ) public onlyOwner {
        serviceValue = _serviceValue;
        sellervalue = sellerfee;    
    }

    function getTokenAddress(
        string memory _type
    ) public view returns (address) {
        return tokentype[_type];
    }
    
    function addTokenType(
        string[] memory _type,
        address[] memory tokenAddress
    ) public onlyOwner {
        require(
            _type.length == tokenAddress.length,
            "Not equal for type and tokenAddress"
        );
        for (uint256 i = 0; i < _type.length; i++) {
            tokentype[_type[i]] = tokenAddress[i];
        }
    }

    function pERCent(
        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,
        uint256 _sellervalue
    ) internal pure returns (uint256, uint256, uint256) {
        uint256 fee = pERCent(amount, _serviceValue);
        uint256 roy = pERCent(amount, royal);
        uint256 netamount = 0;
        if (_sellervalue != 0) {
            uint256 fee1 = pERCent(amount, _sellervalue);
            fee = fee.add(fee1);
            netamount = amount.sub(fee1.add(roy));
        } else {
            netamount = amount.sub(roy);
        }
        return (fee, roy, netamount);
    }

    function orderPlace(
        uint256 tokenId,
        uint256 _price,
        address _conAddress,
        uint256 _type,
        string memory _status
    ) public {
        if (
            keccak256(abi.encodePacked((_status))) ==
            keccak256(abi.encodePacked(("order")))
        ) {
            if (_type == 721) {
                require(
                    IERC721Upgradeable(_conAddress).ownerOf(tokenId) ==
                        msg.sender,
                    "Not a Owner"
                );
            } else {
                require(
                    IERC1155Upgradeable(_conAddress).balanceOf(
                        msg.sender,
                        tokenId
                    ) > 0,
                    "Not a Owner"
                );
            }
        }
        require(_price > 0, "Price Must be greater than zero");
        approveStatus[msg.sender][_conAddress] = true;
        Order memory order;
        order.tokenId = tokenId;
        order.price = _price;
        order.contractAddress = _conAddress;
        order.listOrNot = true;
        order_place[msg.sender][tokenId] = order;
        emit OrderPlace(msg.sender, tokenId, _price);
    }

    function cancelOrder(uint256 tokenId) public {
        delete order_place[msg.sender][tokenId];
        emit CancelOrder(msg.sender, tokenId);
    }

    function changePrice(uint256 value, uint256 tokenId) public {
        require(value < order_place[msg.sender][tokenId].price);
        order_place[msg.sender][tokenId].price = value;
        emit ChangePrice(msg.sender, tokenId, value);
    }

    // ids[0] - tokenId, ids[1] - amount, ids[2] -  nooftoken, ids[3] - nftType, isd[4] - collectionId
    function saleToken(
        address payable from,
        uint256[] memory ids,
        address _conAddr
    ) public payable {
        require(
            ids[1] == order_place[from][ids[0]].price.mul(ids[2]) &&
                order_place[from][ids[0]].price.mul(ids[2]) > 0,
            "Order Mismatch"
        );
        _saleToken(from, ids, "ETH", _conAddr);
        if (ids[3] == 721) {
            IERC721Upgradeable(_conAddr).safeTransferFrom(
                from,
                msg.sender,
                ids[0]
            );
            if (order_place[from][ids[0]].price > 0) {
                delete order_place[from][ids[0]];
            }
        } else {
            if (
                IERC1155Upgradeable(_conAddr).balanceOf(from, ids[0]).sub(
                    ids[2]
                ) == 0
            ) {
                if (order_place[from][ids[0]].price > 0) {
                    delete order_place[from][ids[0]];
                }
            }
            IERC1155Upgradeable(_conAddr).safeTransferFrom(
                from,
                msg.sender,
                ids[0],
                ids[2],
                ""
            );
        }
    }

    // ids[0] - tokenId, ids[1] - amount, ids[2] -  nooftoken, ids[3] - nftType
    //ldatas[0] = _royal, ldatas[1] = Tokendecimals, ldatas[2] = approveValue, ldatas[3] = _adminfee,
    //ldatas[4] = roy, ldatas[5] = netamount, ldatas[6] = val
    function _saleToken(
        address payable from,
        uint256[] memory ids,
        string memory bidtoken,
        address _conAddr
    ) internal {
        uint256[7] memory ldatas;
        ldatas[6] = pERCent(ids[1], serviceValue).add(ids[1]);
        address create;
        if (xdsea721 == _conAddr || xdsea1155 == _conAddr) {
            if (ids[3] == 721) {
                (create, ldatas[0]) = XDSea721(xdsea721).getCreatorsAndRoyalty(
                    ids[0]
                );
            } else {
                (create, ldatas[0]) = XDSea1155(xdsea1155)
                    .getCreatorsAndRoyalty(ids[0]);
            }
        }

        if (
            keccak256(abi.encodePacked((bidtoken))) ==
            keccak256(abi.encodePacked(("ETH")))
        ) {
            // require(msg.value == ldatas[6], "Mismatch the msg.value");
            (ldatas[3], ldatas[4], ldatas[5]) = calc(
                ids[1],
                ldatas[0],
                serviceValue,
                sellervalue
            );
            // require(
            //     msg.value == ldatas[3].add(ldatas[4].add(ldatas[5])),
            //     "Missmatch the fees amount"
            // );
            if (ldatas[3] != 0) {
                payable(owner()).transfer(ldatas[3]);
            }
            if (ldatas[4] != 0) {
                payable(create).transfer(ldatas[4]);
            }
            if (ldatas[5] != 0) {
                from.transfer(ldatas[5]);
            }
        } else {
            IERC20Upgradeable t = IERC20Upgradeable(tokentype[bidtoken]);
            ldatas[1] = deci.sub(t.decimals());
            ldatas[2] = t.allowance(msg.sender, address(this));
            (ldatas[3], ldatas[4], ldatas[5]) = calc(
                ids[1],
                ldatas[0],
                serviceValue,
                sellervalue
            );
            if (ldatas[3] != 0) {
                t.transferFrom(
                    msg.sender,
                    owner(),
                    ldatas[3].div(10 ** ldatas[1])
                );
            }
            if (ldatas[4] != 0) {
                t.transferFrom(
                    msg.sender,
                    create,
                    ldatas[4].div(10 ** ldatas[1])
                );
            }
            if (ldatas[5] != 0) {
                t.transferFrom(
                    msg.sender,
                    from,
                    ldatas[5].div(10 ** ldatas[1])
                );
            }
        }
    }

    // ids[0] - tokenId, ids[1] - amount, ids[2] -  nooftoken, ids[3] - nftType
    function saleWithToken(
        string memory bidtoken,
        address payable from,
        uint256[] memory ids,
        address _conAddr
    ) public {
        require(
            ids[1] == order_place[from][ids[0]].price.mul(ids[2]) &&
                order_place[from][ids[0]].price.mul(ids[2]) > 0,
            "Order is Mismatch"
        );
        _saleToken(from, ids, bidtoken, _conAddr);
        if (ids[3] == 721) {
            IERC721Upgradeable(_conAddr).safeTransferFrom(
                from,
                msg.sender,
                ids[0]
            );

            if (order_place[from][ids[0]].price > 0) {
                delete order_place[from][ids[0]];
            }
        } else {
            if (
                IERC1155Upgradeable(_conAddr).balanceOf(from, ids[0]).sub(
                    ids[2]
                ) == 0
            ) {
                if (order_place[from][ids[0]].price > 0) {
                    delete order_place[from][ids[0]];
                }
            }
            IERC1155Upgradeable(_conAddr).safeTransferFrom(
                from,
                msg.sender,
                ids[0],
                ids[2],
                ""
            );
        }
    }

    // ids[0] - tokenId, ids[1] - amount, ids[2] -  nooftoken, ids[3] - nftType, isd[4] - collectionId
    function acceptBId(
        string memory bidtoken,
        address bidaddr,
        uint256[] memory ids,
        address _conAddr
    ) public {
        _acceptBId(bidtoken, bidaddr, owner(), ids, _conAddr);
        if (ids[3] == 721) {
            IERC721Upgradeable(_conAddr).safeTransferFrom(
                msg.sender,
                bidaddr,
                ids[0]
            );
            if (order_place[msg.sender][ids[0]].price > 0) {
                delete order_place[msg.sender][ids[0]];
            }
        } else {
            if (
                IERC1155Upgradeable(_conAddr).balanceOf(msg.sender, ids[0]).sub(
                    ids[2]
                ) == 0
            ) {
                if (order_place[msg.sender][ids[0]].price > 0) {
                    delete order_place[msg.sender][ids[0]];
                }
            }
            IERC1155Upgradeable(_conAddr).safeTransferFrom(
                msg.sender,
                bidaddr,
                ids[0],
                ids[2],
                ""
            );
        }
    }

    // ids[0] - tokenId, ids[1] - amount, ids[2] -  nooftoken, ids[3] - nftType
    //ldatas[0] = _royal, ldatas[1] = Tokendecimals, ldatas[2] = approveValue, ldatas[3] = _adminfee,
    //ldatas[4] = roy, ldatas[5] = netamount, ldatas[6] = val
    function _acceptBId(
        string memory tokenAss,
        address from,
        address admin,
        uint256[] memory ids,
        address _conAddr
    ) internal {
        uint256[7] memory ldatas;
        ldatas[6] = pERCent(ids[1], serviceValue).add(ids[1]);
        address create;
        if (xdsea721 == _conAddr || xdsea1155 == _conAddr) {
            if (ids[3] == 721) {
                (create, ldatas[0]) = XDSea721(xdsea721).getCreatorsAndRoyalty(
                    ids[0]
                );
            } else {
                (create, ldatas[0]) = XDSea1155(xdsea1155)
                    .getCreatorsAndRoyalty(ids[0]);
            }
        }
        IERC20Upgradeable t = IERC20Upgradeable(tokentype[tokenAss]);
        ldatas[1] = deci.sub(t.decimals());
        ldatas[2] = t.allowance(from, address(this));
        (ldatas[3], ldatas[4], ldatas[5]) = calc(
            ids[1],
            ldatas[0],
            serviceValue,
            sellervalue
        );
        if (ldatas[3] != 0) {
            t.transferFrom(from, admin, ldatas[3].div(10 ** ldatas[1]));
        }
        if (ldatas[4] != 0) {
            t.transferFrom(from, create, ldatas[4].div(10 ** ldatas[1]));
        }
        if (ldatas[5] != 0) {
            t.transferFrom(from, msg.sender, ldatas[5].div(10 ** ldatas[1]));
        }
    }

    // datas[0] - supply, datas[1] - nftType, datas[2] -  royal, datas[3] - price
    function minting(
        string memory ipfsmetadata,
        uint256[] memory datas,
        string memory status
    ) public {
        require(
            msg.sender == owner() || publicMint == true,
            "Public Mint Not Available"
        );
        _tid = _tid.add(1);
        uint256 id_ = _tid.add(block.timestamp);

        if (datas[1] == 721) {
            XDSea721(xdsea721).mint(ipfsmetadata, msg.sender, datas[2], id_);
            if (datas[3] > 0) {
                orderPlace(id_, datas[3], xdsea721, datas[1], "mint");
            } else {
                emit OrderPlace(msg.sender, id_, datas[3]);
            }
        } else {
            XDSea1155(xdsea1155).mint(
                ipfsmetadata,
                msg.sender,
                datas[0],
                datas[2],
                id_
            );
            if (datas[3] > 0) {
                orderPlace(id_, datas[3], xdsea1155, datas[1], "mint");
            } else {
                emit OrderPlace(msg.sender, id_, datas[3]);
            }
        }
        emit Create(msg.sender, id_, status);
    }

    function enablePublicMint() public onlyOwner {
        publicMint = true;
    }

    function disablePublicMint() public onlyOwner {
        publicMint = false;
    }

    function setCollectionAddress(
        address _xdsea721,
        address _xdsea1155
    ) public onlyOwner {
        xdsea721 = _xdsea721;
        xdsea1155 = _xdsea1155;
    }

    function getTokenID(uint256 count) external returns (uint256) {
        require(
            msg.sender == xdsea721 || msg.sender == xdsea1155,
            "Permission denied"
        );
        _tid = _tid.add(count);
        return _tid;
    }