/**
 * encoding.js
 *
 * This NPM module provide encoding and decoding utilities for interacting with
 * dFusion smart contracts where manual byte encoding was needed because of
 * solidity ABI limitations.
 */
import BN from "bn.js";
import assert from "assert";
class OrderBuffer {
    constructor(bytes) {
        this.bytes = bytes;
        this.index = 2; // skip '0x'
        this.readBytes = (size) => this.bytes.slice(this.index, (this.index += size * 2));
        this.decodeAddr = () => `0x${this.readBytes(20)}`;
        this.decodeInt = (size) => new BN(this.readBytes(size / 8), 16);
        this.decodeNumber = (size) => parseInt(this.readBytes(size / 8), 16);
        this.hasMoreBytes = () => this.index < this.bytes.length;
    }
}
function decodeOrder(bytes) {
    return {
        user: bytes.decodeAddr(),
        sellTokenBalance: bytes.decodeInt(256),
        buyToken: bytes.decodeNumber(16),
        sellToken: bytes.decodeNumber(16),
        validFrom: bytes.decodeNumber(32),
        validUntil: bytes.decodeNumber(32),
        priceNumerator: bytes.decodeInt(128),
        priceDenominator: bytes.decodeInt(128),
        remainingAmount: bytes.decodeInt(128),
    };
}
function decodeIndexedOrder(bytes) {
    return {
        ...decodeOrder(bytes),
        orderId: bytes.decodeNumber(16),
    };
}
function decodeOrdersInternal(bytes, decodeFunction, width) {
    if (bytes === null || bytes === undefined || bytes.length === 0) {
        return [];
    }
    assert((bytes.length - 2) % width === 0, "malformed bytes");
    const buffer = new OrderBuffer(bytes);
    const result = [];
    while (buffer.hasMoreBytes()) {
        result.push(decodeFunction(buffer));
    }
    return result;
}
/**
 * Decodes a byte-encoded variable length array of orders. This can be used to
 * decode the result of `BatchExchange.getEncodedUserOrders` and
 * `BatchExchange.getEncodedOrders`.
 */
export function decodeOrders(bytes) {
    return decodeOrdersInternal(bytes, decodeOrder, 112);
}
/**
 * Decodes a byte-encoded variable length array of orders and their indices.
 * This can be used to decode the result of `BatchExchangeViewer.getOpenOrderBook` and
 * `BatchExchangeViewer.getFinalizedOrderBook`.
 */
export function decodeIndexedOrders(bytes) {
    return decodeOrdersInternal(bytes, decodeIndexedOrder, 114);
}
class OrderEncoder {
    constructor(bytes) {
        this.bytes = bytes;
        this.index = 0;
        this.view = new DataView(bytes.buffer);
    }
    encodeHex(hex) {
        const len = hex.length / 2;
        for (let i = 0; i < len; i++) {
            this.bytes[i + this.index] = parseInt(hex.substr(i * 2, 2), 16);
        }
        this.index += len;
    }
    encodeAddr(addr) {
        assert(addr.length === 42 && addr.substr(0, 2) === "0x", `invalid Ethereum address '${addr}`);
        this.encodeHex(addr.substr(2));
    }
    encodeInt(size, value) {
        const hex = typeof value === "string"
            ? BigInt(value).toString(16)
            : value.toString(16);
        assert(hex.length < size * 2, `value ${value} overflows ${size} bits`);
        this.encodeHex(hex.padStart(size / 4, "0"));
    }
    encodeNumber(size, value) {
        let setter;
        switch (size) {
            case 16:
                setter = "setUint16";
                break;
            case 32:
                setter = "setUint32";
                break;
        }
        this.view[setter](this.index, value, false);
        this.index += size / 8;
    }
    encodeOrder(order) {
        this.encodeAddr(order.user);
        this.encodeInt(256, order.sellTokenBalance);
        this.encodeNumber(16, order.buyToken);
        this.encodeNumber(16, order.sellToken);
        this.encodeNumber(32, order.validFrom);
        this.encodeNumber(32, order.validUntil);
        this.encodeInt(128, order.priceNumerator);
        this.encodeInt(128, order.priceDenominator);
        this.encodeInt(128, order.remainingAmount);
    }
    encodeIndexedOrder(order) {
        this.encodeOrder(order);
        this.encodeNumber(16, order.orderId);
    }
}
function encodeOrdersInternal(orders, stride, encode) {
    const bytes = new Uint8Array(orders.length * stride);
    const encoder = new OrderEncoder(bytes);
    for (const order of orders) {
        encode(encoder, order);
    }
    return bytes;
}
/**
 * Encodes an array of orders into a `Uint8Array` of bytes. This uses the same
 * format as `BatchExchange.getEncodedOrders`.
 */
export function encodeOrders(orders) {
    return encodeOrdersInternal(orders, 112, (encoder, order) => encoder.encodeOrder(order));
}
/**
 * Encodes an array of indexed orders into a `Uint8Array` of bytes. This uses
 * the same format as `BatchExchangeViewer.getFilteredOrderBook`.
 */
export function encodeIndexedOrders(orders) {
    return encodeOrdersInternal(orders, 114, (encoder, order) => encoder.encodeIndexedOrder(order));
}
