// An order desk's FIX link to its broker: a session logged on (heartbeats, test requests and
// resend requests answered on its own thread), orders sent as NewOrderSingle and their
// ExecutionReports read back, a cancel the broker refuses, and messages built and checked
// without a network: a group of parties, a CheckSum that does not match.
//
//   FIX_HOST=broker.test:9878 quillo run fix_protocol.qwe      then open http://localhost:8080/order
//
// Needs a FIX 4.4 acceptor at FIX_HOST that knows us as DESK and itself as BROKER (a broker's
// test gateway, or QuickFIX's executor example). FIX_TLS=1 and FIX_CA for FIX over TLS.

use "fix_protocol.qwer"

let HOST = env("FIX_HOST", "127.0.0.1:9878")

// Logs on the first time; after that it says how the session is.
fn link() = fix_connect("broker", HOST, {
    sender: "DESK",
    target: "BROKER",
    begin: "FIX.4.4",
    heartbeat: 30,
    reset: true,
    tls: env("FIX_TLS", "") == "1",
    ca: env("FIX_CA", ""),
    timeout_ms: 5000,
})

// The execution reports for one order, read until it is done or 3 s have passed.
fn reports_for(id) {
    let got = []
    let done = false
    let tries = 0
    while !done && tries < 10 {
        let batch = fix_receive("broker", 20, 300)?
        for m in batch {
            if m.fields.ClOrdID == id { got = got + [m.order] }
            if m.fields.ClOrdID == id && (m.order.status == "filled" || m.order.status == "rejected" || m.order.status == "canceled") { done = true }
        }
        tries += 1
    }
    got
}

route GET /order {
    link()?
    let sent = fix_order("broker", { symbol: "AAPL", side: "buy", qty: 100, type: "limit", price: 187.5, tif: "day", account: "ACC-7" })?
    { cl_ord_id: sent.cl_ord_id, reports: reports_for(sent.cl_ord_id) }
}

route GET /market {
    link()?
    let sent = fix_order("broker", { symbol: "MSFT", side: "sell", qty: 40 })?
    let r = reports_for(sent.cl_ord_id)
    { statuses: map(r, (x) => x.status), price: len(r) > 0 ? r[len(r) - 1].last_px : null }
}

route GET /too-big {
    link()?
    let sent = fix_order("broker", { symbol: "AAPL", side: "buy", qty: 50000, type: "market" })?
    let r = reports_for(sent.cl_ord_id)
    { status: len(r) > 0 ? r[0].status : null, why: len(r) > 0 ? r[0].text : null }
}

route GET /cancel {
    link()?
    fix_send("broker", "OrderCancelRequest", { OrigClOrdID: "NOT-MINE", ClOrdID: "CXL-1", Symbol: "AAPL", Side: 1, TransactTime: "20261030-10:15:00.000", OrderQty: 100 })?
    let answer = fix_receive("broker", 1, 3000)?
    { type: len(answer) > 0 ? answer[0].name : null, text: len(answer) > 0 ? answer[0].fields.Text : null }
}

route GET /status = fix_status("broker")?

route GET /logout = fix_logout("broker", "end of day")?

// No network: an order with two parties built (shown with | for SOH) and read back, and the same
// message with one byte changed.
route GET /offline {
    let order = fix_build("NewOrderSingle", {
        ClOrdID: "ORD-77", Symbol: "IBM", Side: 2, OrderQty: 300, OrdType: 2, Price: 141.2, TimeInForce: 0,
        NoPartyIDs: [{ PartyID: "TRDR1", PartyIDSource: "D", PartyRole: 11 }, { PartyID: "DESKA", PartyIDSource: "D", PartyRole: 3 }],
        TransactTime: "20261030-10:15:00.000",
    }, { sender: "DESK", target: "BROKER", seq: 12, sep: "|" })?
    let read = fix_parse(order, null)?
    {
        message: order,
        valid: read.valid,
        parties: read.fields.NoPartyIDs,
        meanings: read.meanings,
        tampered: fix_parse(replace(order, "300", "900"), null)?.problems,
        tag: fix_tag("ExecType")?,
    }
}
