contract NiftyPlanetMarketplace is Ownable {
    using SafeMath for uint256;

    // Structs
    struct Item {
        address collection;
        uint256 id;
    }
    struct Position {
        Item item;
        uint256 amount;
        uint256 price;
        address owner;
    }

    // Variables
    mapping (uint256 => Position) public positions;
    uint256 public positionsCount = 0;
    address private beneficiaryAddress;
    uint256 private beneficiaryFee = 100;

    // Events
    event PositionCreated(address indexed owner, address indexed collection, uint256 indexed tokenId, uint256 id);
    event PositionDeleted(address operator, uint256 position, address indexed collection, uint256 indexed tokenId);
    event PositionPurchased(address indexed owner, uint256 position, address indexed collection, uint256 indexed token, uint256 amount, uint256 price, address buyer);

    // —
    constructor(address _beneficiaryAddress) {
        beneficiaryAddress = _beneficiaryAddress;
    }

    // —
    function changeBeneficiary(address _beneficiaryAddress) external onlyOwner {
        beneficiaryAddress = _beneficiaryAddress;
    }

    // —
    function changeBeneficiaryFee(uint256 _beneficiaryFee) external onlyOwner {
        beneficiaryFee = _beneficiaryFee;
    }

    // —
    function getFee(uint256 _sum, uint256 _fee) internal pure returns(uint256) {
        return _sum.mul(_fee).div(10000);
    }

    // —
    function createPosition(address _collection, uint256 _id, uint256 _amount, uint256 _price) external returns(uint256) {
        require(Collection(_collection).balanceOf(msg.sender, _id) >= _amount, "Create: not enough amount of NFT on owner's balance");
        require(Collection(_collection).creators(_id) != address(0), "Create: creator is null");

        positionsCount++;
        positions[positionsCount] = Position(Item(_collection, _id), _amount, _price, msg.sender);

        emit PositionCreated(msg.sender, _collection, _id, positionsCount);
        return positionsCount;
    }

    // —
    function deletePosition(uint256 _id) external {
        require(msg.sender == positions[_id].owner || msg.sender == this.owner(), "Delete: access restricted");
        positions[_id].amount = 0;

        emit PositionDeleted(msg.sender, _id, positions[_id].item.collection, positions[_id].item.id);
    }

    // —
    function purchasePosition(uint256 _position, uint256 _amount, address _buyer, bytes calldata _data) payable external {
        Position memory position = positions[_position];
        Collection collection = Collection(position.item.collection);
        require(position.amount >= _amount, "Purchase: not enough amount of items on marketplace");
        require(position.price <= msg.value, "Purchase: wrong transaction value");

        if (_buyer == address(0x0)) {
            _buyer = msg.sender;
        }

        require(transferWithFees(_position, _amount) == true, "Purchase: fees not transferred");
        collection.safeTransferFrom(position.owner, _buyer, position.item.id, _amount, _data);
        emit PositionPurchased(position.owner, _position, position.item.collection, position.item.id, _amount, position.price, _buyer);
    }

    // —
    function transferWithFees(uint256 _position, uint256 _amount) internal returns(bool) {
        Position storage position = positions[_position];
        uint256 sum = position.price.mul(_amount);
        uint256 fee = getFee(sum, beneficiaryFee);
        uint256 total = sum.add(fee);
        require(msg.value >= total, "Fees: insufficient balance");

        uint256 rollback = msg.value.sub(total);
        if(rollback > 0) {
            // Rollback transaction
            payable(msg.sender).transfer(rollback);

            return false;
        } else {
            // Proceed transfers
            position.amount = position.amount.sub(_amount);

            // Beneficiary fees
            if (fee > 0) {
                payable(beneficiaryAddress).transfer(fee);
            }

            // Owner and other fees
            uint256 fees = transferFees(position.item, sum);
            payable(position.owner).transfer(sum.sub(fees));

            return true;
        }
    }

    // —
    function transferFees(Item memory _item, uint256 _sum) internal returns(uint256) {
        Collection collection = Collection(_item.collection);
        uint[] memory fees = collection.getFeeBps(_item.id);
        address[] memory recipients = collection.getFeeRecipients(_item.id);
        uint256 total = 0;

        for (uint256 i = 0; i < fees.length; i++) {
            uint256 fee = getFee(_sum, fees[i]);
            if (fee > 0) {
                payable(recipients[i]).transfer(fee);
                total = total.add(fee);
            }
        }
        return total;
    }
}
/**
 * Marketplace Contract
 */
function purchasePosition(uint256 _position, uint256 _amount, address _buyer, bytes calldata _data) payable external {
    Position memory position = positions[_position];
    Collection collection = Collection(position.item.collection);
    require(position.amount >= _amount, "Purchase: not enough amount of items on marketplace");
    require(position.price <= msg.value, "Purchase: wrong transaction value");

    if (_buyer == address(0x0)) {
        _buyer = msg.sender;
    }

    require(transferWithFees(_position, _amount) == true, "Purchase: fees not transferred");
    collection.safeTransferFrom(position.owner, _buyer, position.item.id, _amount, _data);
    emit PositionPurchased(position.owner, _position, position.item.collection, position.item.id, _amount, position.price, _buyer);
}

// —