//go:build !windows && !freebsd && !openbsd && !netbsd
// +build !windows,!freebsd,!openbsd,!netbsd

package ble

import (
	"encoding/binary"
	"fmt"
	"strconv"
	"strings"

	"github.com/bettercap/bettercap/v2/network"
	"github.com/bettercap/gatt"

	"github.com/evilsocket/islazy/tui"
)

var appearances = map[uint16]string{
	0:    "Unknown",
	64:   "Generic Phone",
	128:  "Generic Computer",
	192:  "Generic Watch",
	193:  "Watch: Sports Watch",
	256:  "Generic Clock",
	320:  "Generic Display",
	384:  "Generic Remote Control",
	448:  "Generic Eye-glasses",
	512:  "Generic Tag",
	576:  "Generic Keyring",
	640:  "Generic Media Player",
	704:  "Generic Barcode Scanner",
	768:  "Generic Thermometer",
	769:  "Thermometer: Ear",
	832:  "Generic Heart rate Sensor",
	833:  "Heart Rate Sensor: Heart Rate Belt",
	896:  "Generic Blood Pressure",
	897:  "Blood Pressure: Arm",
	898:  "Blood Pressure: Wrist",
	960:  "Human Interface Device (HID)",
	961:  "Keyboard",
	962:  "Mouse",
	963:  "Joystick",
	964:  "Gamepad",
	965:  "Digitizer Tablet",
	966:  "Card Reader",
	967:  "Digital Pen",
	968:  "Barcode Scanner",
	1024: "Generic Glucose Meter",
	1088: "Generic: Running Walking Sensor",
	1089: "Running Walking Sensor: In-Shoe",
	1090: "Running Walking Sensor: On-Shoe",
	1091: "Running Walking Sensor: On-Hip",
	1152: "Generic: Cycling",
	1153: "Cycling: Cycling Computer",
	1154: "Cycling: Speed Sensor",
	1155: "Cycling: Cadence Sensor",
	1156: "Cycling: Power Sensor",
	1157: "Cycling: Speed and Cadence Sensor",
	1216: "Generic Control Device",
	1217: "Switch",
	1218: "Multi-switch",
	1219: "Button",
	1220: "Slider",
	1221: "Rotary",
	1222: "Touch-panel",
	1280: "Generic Network Device",
	1281: "Access Point",
	1344: "Generic Sensor",
	1345: "Motion Sensor",
	1346: "Air Quality Sensor",
	1347: "Temperature Sensor",
	1348: "Humidity Sensor",
	1349: "Leak Sensor",
	1350: "Smoke Sensor",
	1351: "Occupancy Sensor",
	1352: "Contact Sensor",
	1353: "Carbon Monoxide Sensor",
	1354: "Carbon Dioxide Sensor",
	1355: "Ambient Light Sensor",
	1356: "Energy Sensor",
	1357: "Color Light Sensor",
	1358: "Rain Sensor",
	1359: "Fire Sensor",
	1360: "Wind Sensor",
	1361: "Proximity Sensor",
	1362: "Multi-Sensor",
	1408: "Generic Light Fixtures",
	1409: "Wall Light",
	1410: "Ceiling Light",
	1411: "Floor Light",
	1412: "Cabinet Light",
	1413: "Desk Light",
	1414: "Troffer Light",
	1415: "Pendant Light",
	1416: "In-ground Light",
	1417: "Flood Light",
	1418: "Underwater Light",
	1419: "Bollard with Light",
	1420: "Pathway Light",
	1421: "Garden Light",
	1422: "Pole-top Light",
	1423: "Spotlight",
	1424: "Linear Light",
	1425: "Street Light",
	1426: "Shelves Light",
	1427: "High-bay / Low-bay Light",
	1428: "Emergency Exit Light",
	1472: "Generic Fan",
	1473: "Ceiling Fan",
	1474: "Axial Fan",
	1475: "Exhaust Fan",
	1476: "Pedestal Fan",
	1477: "Desk Fan",
	1478: "Wall Fan",
	1536: "Generic HVAC",
	1537: "Thermostat",
	1600: "Generic Air Conditioning",
	1664: "Generic Humidifier",
	1728: "Generic Heating",
	1729: "Radiator",
	1730: "Boiler",
	1731: "Heat Pump",
	1732: "Infrared Heater",
	1733: "Radiant Panel Heater",
	1734: "Fan Heater",
	1735: "Air Curtain",
	1792: "Generic Access Control",
	1793: "Access Door",
	1794: "Garage Door",
	1795: "Emergency Exit Door",
	1796: "Access Lock",
	1797: "Elevator",
	1798: "Window",
	1799: "Entrance Gate",
	1856: "Generic Motorized Device",
	1857: "Motorized Gate",
	1858: "Awning",
	1859: "Blinds or Shades",
	1860: "Curtains",
	1861: "Screen",
	1920: "Generic Power Device",
	1921: "Power Outlet",
	1922: "Power Strip",
	1923: "Plug",
	1924: "Power Supply",
	1925: "LED Driver",
	1926: "Fluorescent Lamp Gear",
	1927: "HID Lamp Gear",
	1984: "Generic Light Source",
	1985: "Incandescent Light Bulb",
	1986: "LED Bulb",
	1987: "HID Lamp",
	1988: "Fluorescent Lamp",
	1989: "LED Array",
	1990: "Multi-Color LED Array",
	3136: "Generic: Pulse Oximeter",
	3137: "Fingertip",
	3138: "Wrist Worn",
	3200: "Generic: Weight Scale",
	3264: "Generic",
	3265: "Powered Wheelchair",
	3266: "Mobility Scooter",
	3328: "Generic",
	5184: "Generic: Outdoor Sports Activity",
	5185: "Location Display Device",
	5186: "Location and Navigation Display Device",
	5187: "Location Pod",
	5188: "Location and Navigation Pod",
}

func parseProperties(ch *gatt.Characteristic) (props []string, isReadable bool, isWritable bool, withResponse bool) {
	isReadable = false
	isWritable = false
	withResponse = false
	props = make([]string, 0)
	mask := ch.Properties()

	if (mask & gatt.CharBroadcast) != 0 {
		props = append(props, "BCAST")
	}
	if (mask & gatt.CharRead) != 0 {
		isReadable = true
		props = append(props, "READ")
	}
	if (mask&gatt.CharWriteNR) != 0 || (mask&gatt.CharWrite) != 0 {
		props = append(props, tui.Bold("WRITE"))
		isWritable = true
		withResponse = (mask & gatt.CharWriteNR) == 0
	}
	if (mask & gatt.CharNotify) != 0 {
		props = append(props, "NOTIFY")
	}
	if (mask & gatt.CharIndicate) != 0 {
		props = append(props, "INDICATE")
	}
	if (mask & gatt.CharSignedWrite) != 0 {
		props = append(props, tui.Yellow("SIGN WRITE"))
		isWritable = true
		withResponse = true
	}
	if (mask & gatt.CharExtended) != 0 {
		props = append(props, "X")
	}

	return
}

func parseRawData(raw []byte) string {
	s := ""
	for _, b := range raw {
		if strconv.IsPrint(rune(b)) {
			s += tui.Yellow(string(b))
		} else {
			s += tui.Dim(fmt.Sprintf("%02x", b))
		}
	}
	return s
}

// org.bluetooth.characteristic.gap.appearance
func parseAppearance(raw []byte) string {
	app := binary.LittleEndian.Uint16(raw[0:2])
	if appName, found := appearances[app]; found {
		return tui.Green(appName)
	}
	return fmt.Sprintf("0x%x", app)
}

// org.bluetooth.characteristic.pnp_id
func parsePNPID(raw []byte) []string {
	vendorIdSrc := byte(raw[0])
	vendorId := binary.LittleEndian.Uint16(raw[1:3])
	prodId := binary.LittleEndian.Uint16(raw[3:5])
	prodVer := binary.LittleEndian.Uint16(raw[5:7])

	src := ""
	if vendorIdSrc == 1 {
		src = " (Bluetooth SIG assigned Company Identifier)"
	} else if vendorIdSrc == 2 {
		src = " (USB Implementer’s Forum assigned Vendor ID value)"
	}

	return []string{
		tui.Green("Vendor ID") + fmt.Sprintf(": 0x%04x%s", vendorId, tui.Dim(src)),
		tui.Green("Product ID") + fmt.Sprintf(": 0x%04x", prodId),
		tui.Green("Product Version") + fmt.Sprintf(": 0x%04x", prodVer),
	}
}

// org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters
func parseConnectionParams(raw []byte) []string {
	minConInt := binary.LittleEndian.Uint16(raw[0:2])
	maxConInt := binary.LittleEndian.Uint16(raw[2:4])
	slaveLat := binary.LittleEndian.Uint16(raw[4:6])
	conTimeMul := binary.LittleEndian.Uint16(raw[6:8])

	return []string{
		tui.Green("Connection Interval") + fmt.Sprintf(": %d -> %d", minConInt, maxConInt),
		tui.Green("Slave Latency") + fmt.Sprintf(": %d", slaveLat),
		tui.Green("Connection Supervision Timeout Multiplier") + fmt.Sprintf(": %d", conTimeMul),
	}
}

// org.bluetooth.characteristic.gap.peripheral_privacy_flag
func parsePrivacyFlag(raw []byte) string {
	if raw[0] == 0x0 {
		return tui.Green("Privacy Disabled")
	}
	return tui.Red("Privacy Enabled")
}

func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) {
	columns := []string{"Handles", "Service > Characteristics", "Properties", "Data"}
	rows := make([][]string, 0)

	wantsToWrite := mod.writeUUID != nil
	foundToWrite := false

	mod.currDevice.Services = make([]network.BLEService, 0)

	for _, svc := range services {
		service := network.BLEService{
			UUID:            svc.UUID().String(),
			Name:            svc.Name(),
			Handle:          svc.Handle(),
			EndHandle:       svc.EndHandle(),
			Characteristics: make([]network.BLECharacteristic, 0),
		}

		mod.Session.Events.Add("ble.device.service.discovered", svc)

		name := svc.Name()
		if name == "" {
			name = svc.UUID().String()
		} else {
			name = fmt.Sprintf("%s (%s)", tui.Green(name), tui.Dim(svc.UUID().String()))
		}

		row := []string{
			fmt.Sprintf("%04x -> %04x", svc.Handle(), svc.EndHandle()),
			name,
			"",
			"",
		}

		rows = append(rows, row)

		chars, err := p.DiscoverCharacteristics(nil, svc)
		if err != nil {
			mod.Error("error while enumerating chars for service %s: %s", svc.UUID(), err)
		} else {
			for _, ch := range chars {
				props, isReadable, isWritable, withResponse := parseProperties(ch)

				char := network.BLECharacteristic{
					UUID:       ch.UUID().String(),
					Name:       ch.Name(),
					Handle:     ch.VHandle(),
					Properties: props,
				}

				mod.Session.Events.Add("ble.device.characteristic.discovered", ch)

				name = ch.Name()
				if name == "" {
					name = "    " + ch.UUID().String()
				} else {
					name = fmt.Sprintf("    %s (%s)", tui.Green(name), tui.Dim(ch.UUID().String()))
				}

				if wantsToWrite && mod.writeUUID.Equal(ch.UUID()) {
					foundToWrite = true
					if isWritable {
						mod.Debug("writing %d bytes to characteristics %s ...", len(mod.writeData), mod.writeUUID)
					} else {
						mod.Warning("attempt to write %d bytes to non writable characteristics %s ...", len(mod.writeData), mod.writeUUID)
					}

					if err := p.WriteCharacteristic(ch, mod.writeData, !withResponse); err != nil {
						mod.Error("error while writing: %s", err)
					}
				}

				sz := 0
				raw := ([]byte)(nil)
				err := error(nil)
				if isReadable {
					if raw, err = p.ReadCharacteristic(ch); raw != nil {
						sz = len(raw)
					}
				}

				data := ""
				multi := ([]string)(nil)
				if err != nil {
					data = tui.Red(err.Error())
				} else if ch.Name() == "Appearance" && sz >= 2 {
					data = parseAppearance(raw)
				} else if ch.Name() == "PnP ID" && sz >= 7 {
					multi = parsePNPID(raw)
				} else if ch.Name() == "Peripheral Preferred Connection Parameters" && sz >= 8 {
					multi = parseConnectionParams(raw)
				} else if ch.Name() == "Peripheral Privacy Flag" && sz >= 1 {
					data = parsePrivacyFlag(raw)
				} else {
					data = parseRawData(raw)
				}

				if ch.Name() == "Device Name" && data != "" && mod.currDevice.DeviceName == "" {
					mod.currDevice.DeviceName = data
				}

				if multi == nil {
					char.Data = data
					rows = append(rows, []string{
						fmt.Sprintf("%04x", ch.VHandle()),
						name,
						strings.Join(props, ", "),
						data,
					})
				} else {
					char.Data = multi
					for i, m := range multi {
						if i == 0 {
							rows = append(rows, []string{
								fmt.Sprintf("%04x", ch.VHandle()),
								name,
								strings.Join(props, ", "),
								m,
							})
						} else {
							rows = append(rows, []string{"", "", "", m})
						}
					}
				}

				service.Characteristics = append(service.Characteristics, char)
			}
			// blank row after every service, bleah style
			rows = append(rows, []string{"", "", "", ""})
		}

		mod.currDevice.Services = append(mod.currDevice.Services, service)
	}

	if wantsToWrite && !foundToWrite {
		mod.Error("writable characteristics %s not found.", mod.writeUUID)
	} else {
		tui.Table(mod.Session.Events.Stdout, columns, rows)
		mod.Session.Refresh()
	}
}
