contract OwnersExchange is Ownable {

    using SafeMath for uint;
    using itmap for itmap.itmap;

    enum ORDER_TYPE {
        NULL, BUY, SELL
    }
    uint public orderCount;
    uint public fee;
    uint public lockedFees;
    uint public totalFees;
    mapping(uint => uint) public feeBalances;
    address[] public addressRegistry; 
    mapping(address => uint) public addressIndex;

    itmap.itmap orderBook;

    PoolOwnersInterface public poolOwners;
    ERC20 public feeToken;

    event NewOrder(ORDER_TYPE indexed orderType, address indexed sender, uint price, uint amount);
    event OrderRemoved(ORDER_TYPE indexed orderType, address indexed sender, uint price, uint amount);
    event OrderFilled(ORDER_TYPE indexed orderType, address indexed sender, address receiver, uint price, uint amount);

    /**
        @dev Initialise the contract
        @param _poolOwners Set the address of the PoolOwners contract used in this DEX
     */
    constructor(address _poolOwners, address _feeToken) public {
        require(_poolOwners != address(0), "_poolOwners needs to be set");
        poolOwners = PoolOwnersInterface(_poolOwners);
        feeToken = ERC20(_feeToken);
        addressRegistry.push(address(0));
        orderCount = 1;
    }

    /**
        @dev Register an address to a uint allowing packing in orders
        @param _address The address to register
     */
    function addressRegister(address _address) private returns (uint) {
        if (addressIndex[_address] != 0) {
            return addressIndex[_address];
        } else {
            require(addressRegistry.length < 1 << 32, "Registered addresses hit maximum");
            addressIndex[_address] = addressRegistry.length;
            addressRegistry.push(_address);
            return addressRegistry.length - 1;
        }
    }

    /**
        @dev ERC677 Reciever for fee token transfer (Always expected to be LINK)
        @param _sender The address of the sender of the token
        @param _value The amount of token received
        @param _data Extra data, not needed in this use-case
     */
    function onTokenTransfer(address _sender, uint256 _value, bytes _data) public {
        require(msg.sender == address(feeToken), "Sender needs to be the fee token");
        uint index = addressRegister(_sender);
        feeBalances[index] = feeBalances[index].add(_value);
        totalFees = totalFees.add(_value);
    }

    /**
        @dev Allow users to withdraw any tokens used for fees
        @param _value The amount wanting to be withdrawn
     */
    function withdrawFeeToken(uint256 _value) public {
        uint index = addressRegister(msg.sender);
        require(feeBalances[index] >= _value, "You're withdrawing more than your balance");
        feeBalances[index] = feeBalances[index].sub(_value);
        totalFees = totalFees.sub(_value);
        if (feeBalances[index] == 0) {
            delete feeBalances[index];
        }
        feeToken.transfer(msg.sender, _value);
    }

    /**
        @dev Set the fee percentage
        @param _fee The percentage of fees to be taken in LINK
     */
    function setFee(uint _fee) public onlyOwner {
        require(_fee <= 500 finney, "Fees can't be more than 50%");
        fee = _fee;
    }

    /**
        @dev Returns the fee cost based on a price & amount
        @param _price The price of the order
        @param _amount The amount requested
     */
    function feeForOrder(uint _price, uint _amount) public view returns (uint) {
        return _price
            .mul(_amount)
            .div(1 ether)
            .mul(fee)
            .div(1 ether);
    }

    /**
        @dev Returns the ETH cost of an order
        @param _price The price of the order
        @param _amount The amount requested
     */
    function costOfOrder(uint _price, uint _amount) public pure returns (uint) {
        return _price.mul(_amount).div(1 ether);
    }

    /**
        @dev Create a new sell order
        @param _price The price of the order per 1 ether of token
        @param _amount The amount of tokens being sent
     */
    function addSellOrder(uint _price, uint _amount) public {
        require(is111bit(_price) && is111bit(_amount), "Price or amount exceeds 111 bits");

        require(_price > 0, "Price needs to be greater than 0");
        require(_amount > 0, "Amount needs to be greater than 0");

        uint orderFee = feeForOrder(_price, _amount);
        uint index = addressRegister(msg.sender);
        if (orderFee > 0) {
            require(feeBalances[index] >= orderFee, "You do not have enough deposited for fees");
            feeBalances[index] = feeBalances[index].sub(orderFee);
            feeBalances[0] = feeBalances[0].add(orderFee);
            lockedFees = lockedFees.add(orderFee);
        }
        poolOwners.sendOwnershipFrom(msg.sender, this, _amount);

        require(
            !orderBook.insert(orderCount, (((uint(ORDER_TYPE.SELL) << 32 | index) << 111 | _price) << 111) | _amount), 
            "Map replacement detected"
        );
        orderCount += 1;
    
        emit NewOrder(ORDER_TYPE.SELL, msg.sender, _price, _amount);
    }

    /**
        @dev Add a new buy order, ETH sent needs to equal: (price * amount) / 18
        @param _price The price of the buy order per 1 ether of LP token
        @param _amount The amount of tokens wanting to be purchased
     */
    function addBuyOrder(uint _price, uint _amount) public payable {
        require(is111bit(_price) && is111bit(_amount), "Price or amount exceeds 111 bits");

        require(_price > 0, "Price needs to be greater than 0");
        require(_amount > 0, "Amount needs to be greater than 0");

        uint orderFee = feeForOrder(_price, _amount);
        uint index = addressRegister(msg.sender);
        if (orderFee > 0) {
            require(feeBalances[index] >= orderFee, "You do not have enough deposited for fees");
            feeBalances[index] = feeBalances[index].sub(orderFee);
            feeBalances[0] = feeBalances[0].add(orderFee);
            lockedFees = lockedFees.add(orderFee);
        }

        uint cost = _price.mul(_amount).div(1 ether);
        require(_price.mul(_amount) == cost.mul(1 ether), "The price and amount of this order is too small");
        require(msg.value == cost, "ETH sent needs to equal the cost");

        require(
            !orderBook.insert(orderCount, (((uint(ORDER_TYPE.BUY) << 32 | index) << 111 | _price) << 111) | _amount), 
            "Map replacement detected"
        );
        orderCount += 1;
    
        emit NewOrder(ORDER_TYPE.BUY, msg.sender, _price, _amount);
    }

    /**
        @dev Remove a buy order and refund ETH back to the sender
        @param _key The key of the order in the book
     */
    function removeBuyOrder(uint _key) public {
        uint order = orderBook.get(_key);
        ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
        require(orderType == ORDER_TYPE.BUY, "This is not a buy order");
        uint index = addressIndex[msg.sender];
        require(index == (order << 2) >> 224, "You are not the sender of this order");

        uint price = (order << 34) >> 145;
        uint amount = (order << 145) >> 145;
        require(orderBook.remove(_key), "Map remove failed");

        uint orderFee = feeForOrder(price, amount);
        feeBalances[index] = feeBalances[index].add(orderFee);
        feeBalances[0] = feeBalances[0].sub(orderFee);
        lockedFees = lockedFees.sub(orderFee);

        uint cost = price.mul(amount).div(1 ether);
        msg.sender.transfer(cost);

        emit OrderRemoved(orderType, msg.sender, price, amount);
    }

    /**
        @dev Remove a sell order and refund the LP tokens back to the sender
        @param _key The key of the order in the book
     */
    function removeSellOrder(uint _key) public {
        uint order = orderBook.get(_key);
        ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
        require(orderType == ORDER_TYPE.SELL, "This is not a sell order");
        uint index = addressIndex[msg.sender];
        require(index == (order << 2) >> 224, "You are not the sender of this order");

        uint price = (order << 34) >> 145;
        uint amount = (order << 145) >> 145;
        require(orderBook.remove(_key), "Map remove failed");

        uint orderFee = feeForOrder(price, amount);
        feeBalances[index] = feeBalances[index].add(orderFee);
        feeBalances[0] = feeBalances[0].sub(orderFee);
        lockedFees = lockedFees.sub(orderFee);

        poolOwners.sendOwnership(msg.sender, amount);

        emit OrderRemoved(orderType, msg.sender, price, amount);
    }

    /**
        @dev Fill a sell order in the order book
        @dev Orders have to be filled in whole amounts
        @param _key Key of the order as per orderbook
     */
    function fillSellOrder(uint _key) public payable {
        uint order = orderBook.get(_key);
        ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
        require(orderType == ORDER_TYPE.SELL, "This is not a sell order");
        uint index = addressRegister(msg.sender);
        require(index != (order << 2) >> 224, "You cannot fill your own order");

        uint price = (order << 34) >> 145;
        uint amount = (order << 145) >> 145;

        uint orderFee = feeForOrder(price, amount);
        require(feeBalances[index] >= orderFee, "You do not have enough deposited fees to fill this order");

        uint cost = price.mul(amount).div(1 ether);
        require(msg.value == cost, "ETH sent needs to equal the cost");

        require(orderBook.remove(_key), "Map remove failed");

        addressRegistry[index].transfer(msg.value);
        poolOwners.sendOwnership(msg.sender, amount);

        if (orderFee > 0) {
            feeBalances[index] = feeBalances[index].sub(orderFee);
            feeBalances[0] = feeBalances[0].add(orderFee);
            lockedFees = lockedFees.sub(orderFee);
        }

        emit OrderFilled(orderType, addressRegistry[(order << 2) >> 224], msg.sender, price, amount);
    }

    /**
        @dev Fill a buy order in the order book
        @dev Orders have to be filled in whole amounts
        @param _key Key of the order, which is the buyers address
     */
    function fillBuyOrder(uint _key) public {
        uint order = orderBook.get(_key);
        ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
        require(orderType == ORDER_TYPE.BUY, "This is not a buy order");
        uint index = addressRegister(msg.sender);
        require(index != (order << 2) >> 224, "You cannot fill your own order");

        uint price = (order << 34) >> 145;
        uint amount = (order << 145) >> 145;

        uint orderFee = feeForOrder(price, amount);
        require(feeBalances[index] >= orderFee, "You do not have enough deposited fees to fill this order");

        uint cost = price.mul(amount).div(1 ether);
        
        require(orderBook.remove(_key), "Map remove failed");

        msg.sender.transfer(cost);
        poolOwners.sendOwnershipFrom(msg.sender, addressRegistry[(order << 2) >> 224], amount);

        if (orderFee > 0) {
            feeBalances[index] = feeBalances[index].sub(orderFee);
            feeBalances[0] = feeBalances[0].add(orderFee);
            lockedFees = lockedFees.sub(orderFee);
        }

        emit OrderFilled(orderType, addressRegistry[(order << 2) >> 224], msg.sender, price, amount);
    }

    /**
        @dev Send tokens earned via fees back to PoolOwners to be re-distributed
     */
    function withdrawFeesToPoolOwners() public {
        uint feeBalance = feeBalances[0];
        require(feeBalance > lockedFees, "Contract doesn't have a withdrawable fee balance");
        feeBalances[0] = lockedFees;
        uint amount = feeBalance.sub(lockedFees);
        totalFees = totalFees.sub(amount);
        feeToken.transfer(poolOwners, amount);
    }

    /**
        @dev Send any fee token earned via PoolOwners distribution back to be re-distributed
     */
    function withdrawDistributedToPoolOwners() public {
        uint balance = feeToken.balanceOf(this).sub(totalFees);
        require(balance > 0, "There is no distributed fee token balance in the contract");
        feeToken.transfer(poolOwners, balance);
    }

    /**
        @dev Get a single order by its key
        @param _key The key of the order as per the book
     */
    function getOrder(uint _key) public view returns (ORDER_TYPE, address, uint, uint) {
        uint order = orderBook.get(_key);
        return (
            ORDER_TYPE(order >> 254), 
            addressRegistry[(order << 2) >> 224], 
            (order << 34) >> 145, 
            (order << 145) >> 145
        );
    }

    /**
        @dev Get a batch of 10 orders by a given array of keys
        @dev ID's has to be equal or less than 10 in length, or an empty response is given
        @param _start The starting index in the order book to return from
     */
    function getOrders(uint _start) public view returns (
        uint[10] keys,
        address[10] addresses, 
        ORDER_TYPE[10] orderTypes, 
        uint[10] prices, 
        uint[10] amounts
    ) {
        for (uint i = 0; i < 10; i++) {
            if (orderBook.size() == _start + i) {
                break;
            }
            uint key = orderBook.getKey(_start + i);
            keys[i] = key;
            uint order = orderBook.get(key);
            addresses[i] = addressRegistry[(order << 2) >> 224];
            orderTypes[i] = ORDER_TYPE(order >> 254);
            prices[i] = (order << 34) >> 145;
            amounts[i] = (order << 145) >> 145;
        }
        return (keys, addresses, orderTypes, prices, amounts);
    }

    /**
        @dev Get an orderbook key from the orderbook index
        @param _i The index to fetch the key for
     */
    function getOrderBookKey(uint _i) public view returns (uint key) {
        if (_i < orderBook.size()) {
            key = orderBook.getKey(_i);
        } else {
            key = 0;
        }
        return key;
    }

    /**
        @dev Get orderbook keys in batches of 10
        @param _start The start of the index for the batch
     */
    function getOrderBookKeys(uint _start) public view returns (uint[10] keys) {
        for (uint i = 0; i < 10; i++) {
            if (i + _start < orderBook.size()) {
                keys[i] = orderBook.getKey(_start + i);
            } else {
                keys[i] = 0;
            }
        }
        return keys;
    }

    /**
        @dev Get the orderbook size to allow for batch fetching of keys
     */
    function getOrderBookSize() public view returns (uint) {
        return orderBook.size();
    }

    /**
        @dev Verify that the number being passed fits into 111 bits for packing
        @param _val The value to check
     */
    function is111bit(uint _val) private pure returns (bool) {
        return (_val < 1 << 111);
    }

}
/**
 @title OwnersExchange
 @dev Allow for trustless exchange of LP owners tokens
 */
function removeSellOrder(uint _key) public {
    uint order = orderBook.get(_key);
    ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
    require(orderType == ORDER_TYPE.SELL, "This is not a sell order");
    uint index = addressIndex[msg.sender];
    require(index == (order << 2) >> 224, "You are not the sender of this order");

    uint price = (order << 34) >> 145;
    uint amount = (order << 145) >> 145;
    require(orderBook.remove(_key), "Map remove failed");

    uint orderFee = feeForOrder(price, amount);
    feeBalances[index] = feeBalances[index].add(orderFee);
    feeBalances[0] = feeBalances[0].sub(orderFee);
    lockedFees = lockedFees.sub(orderFee);

    poolOwners.sendOwnership(msg.sender, amount);

    emit OrderRemoved(orderType, msg.sender, price, amount);
}

/**
 @dev Remove a sell order and refund the LP tokens back to the sender
 @param _key The key of the order in the book
 */