package usbip

import (
	"encoding/binary"
	"fmt"
	"log"
)

// urbHeaderSize is the fixed 20-byte USB/IP basic header.
const urbHeaderSize = 20

// urbMessageSize is the basic header plus the 28-byte command/return body.
const urbMessageSize = 48

// TraceRequest logs a CMD_SUBMIT or CMD_UNLINK frame travelling from the use
// side towards the share side. Callers must gate this on protocol.Debug.
func TraceRequest(tag string, data []byte) {
	if len(data) < urbHeaderSize {
		return
	}

	cmd := binary.BigEndian.Uint32(data[0:4])
	seqNum := binary.BigEndian.Uint32(data[4:8])
	dir := binary.BigEndian.Uint32(data[12:16])
	ep := binary.BigEndian.Uint32(data[16:20])

	switch cmd {
	case CmdSubmit:
		var extra string
		if ep == 0 && len(data) >= urbMessageSize {
			// The 8-byte setup packet sits at the end of the command body.
			setup := data[urbMessageSize-8 : urbMessageSize]
			extra = fmt.Sprintf(" setup=0x%02x/0x%02x wVal=0x%04x wIdx=0x%04x wLen=%d",
				setup[0], setup[1],
				binary.LittleEndian.Uint16(setup[2:4]),
				binary.LittleEndian.Uint16(setup[4:6]),
				binary.LittleEndian.Uint16(setup[6:8]))
		}
		log.Printf("[%s] -> CMD_SUBMIT seq=%d EP%d %s%s (%d bytes)",
			tag, seqNum, ep, dirName(dir), extra, len(data))

	case CmdUnlink:
		log.Printf("[%s] -> CMD_UNLINK seq=%d (%d bytes)", tag, seqNum, len(data))
	}
}

// TraceResponse logs a RET_SUBMIT or RET_UNLINK frame travelling from the
// share side back to the use side. Callers must gate this on protocol.Debug.
func TraceResponse(tag string, data []byte) {
	if len(data) < urbMessageSize {
		return
	}

	cmd := binary.BigEndian.Uint32(data[0:4])
	seqNum := binary.BigEndian.Uint32(data[4:8])
	dir := binary.BigEndian.Uint32(data[12:16])
	ep := binary.BigEndian.Uint32(data[16:20])
	status := int32(binary.BigEndian.Uint32(data[20:24]))

	switch cmd {
	case RetSubmit:
		actualLen := binary.BigEndian.Uint32(data[24:28])
		var payload string
		if dir == DirIn && actualLen > 0 && len(data) > urbMessageSize {
			end := urbMessageSize + int(actualLen)
			if end > len(data) {
				end = len(data)
			}
			if end-urbMessageSize > 16 {
				end = urbMessageSize + 16
			}
			payload = fmt.Sprintf(" data=%x", data[urbMessageSize:end])
		}
		log.Printf("[%s] <- RET_SUBMIT seq=%d EP%d %s status=%d actual=%d%s",
			tag, seqNum, ep, dirName(dir), status, actualLen, payload)

	case RetUnlink:
		log.Printf("[%s] <- RET_UNLINK seq=%d status=%d", tag, seqNum, status)
	}
}

func dirName(dir uint32) string {
	if dir == DirIn {
		return "IN"
	}
	return "OUT"
}
