package hidrpc

import (
	"encoding/binary"
	"fmt"
)

// Message ..
type Message struct {
	t MessageType
	d []byte
}

// Marshal marshals the message to a byte array.
func (m *Message) Marshal() ([]byte, error) {
	return Marshal(m)
}

func (m *Message) Type() MessageType {
	return m.t
}

func (m *Message) String() string {
	switch m.t {
	case TypeHandshake:
		return "Handshake"
	case TypeKeypressReport:
		if len(m.d) < 2 {
			return fmt.Sprintf("KeypressReport{Malformed: %v}", m.d)
		}
		return fmt.Sprintf("KeypressReport{Key: %d, Press: %v}", m.d[0], m.d[1] == uint8(1))
	case TypeKeyboardReport:
		if len(m.d) < 2 {
			return fmt.Sprintf("KeyboardReport{Malformed: %v}", m.d)
		}
		return fmt.Sprintf("KeyboardReport{Modifier: %d, Keys: %v}", m.d[0], m.d[1:])
	case TypePointerReport:
		if len(m.d) < 9 {
			return fmt.Sprintf("PointerReport{Malformed: %v}", m.d)
		}
		return fmt.Sprintf("PointerReport{X: %d, Y: %d, Button: %d}", m.d[0:4], m.d[4:8], m.d[8])
	case TypeMouseReport:
		if len(m.d) < 3 {
			return fmt.Sprintf("MouseReport{Malformed: %v}", m.d)
		}
		return fmt.Sprintf("MouseReport{DX: %d, DY: %d, Button: %d}", m.d[0], m.d[1], m.d[2])
	case TypeKeypressKeepAliveReport:
		return "KeypressKeepAliveReport"
	case TypeKeyboardMacroReport:
		if len(m.d) < 5 {
			return fmt.Sprintf("KeyboardMacroReport{Malformed: %v}", m.d)
		}
		return fmt.Sprintf("KeyboardMacroReport{IsPaste: %v, Length: %d}", m.d[0] == uint8(1), binary.BigEndian.Uint32(m.d[1:5]))
	default:
		return fmt.Sprintf("Unknown{Type: %d, Data: %v}", m.t, m.d)
	}
}

// KeypressReport ..
type KeypressReport struct {
	Key   byte
	Press bool
}

// KeypressReport returns the keypress report from the message.
func (m *Message) KeypressReport() (KeypressReport, error) {
	if m.t != TypeKeypressReport {
		return KeypressReport{}, fmt.Errorf("invalid message type: %d", m.t)
	}

	return KeypressReport{
		Key:   m.d[0],
		Press: m.d[1] == uint8(1),
	}, nil
}

// KeyboardReport ..
type KeyboardReport struct {
	Modifier byte
	Keys     []byte
}

// KeyboardReport returns the keyboard report from the message.
func (m *Message) KeyboardReport() (KeyboardReport, error) {
	if m.t != TypeKeyboardReport {
		return KeyboardReport{}, fmt.Errorf("invalid message type: %d", m.t)
	}

	return KeyboardReport{
		Modifier: m.d[0],
		Keys:     m.d[1:],
	}, nil
}

// Macro ..
type KeyboardMacroStep struct {
	Modifier byte   // 1 byte
	Keys     []byte // 6 bytes: hidKeyBufferSize
	Delay    uint16 // 2 bytes
}
type KeyboardMacroReport struct {
	IsPaste   bool
	StepCount uint32
	Steps     []KeyboardMacroStep
}

// HidKeyBufferSize is the size of the keys buffer in the keyboard report.
const HidKeyBufferSize = 6

// KeyboardMacroReport returns the keyboard macro report from the message.
func (m *Message) KeyboardMacroReport() (KeyboardMacroReport, error) {
	if m.t != TypeKeyboardMacroReport {
		return KeyboardMacroReport{}, fmt.Errorf("invalid message type: %d", m.t)
	}

	isPaste := m.d[0] == uint8(1)
	stepCount := binary.BigEndian.Uint32(m.d[1:5])

	// check total length
	expectedLength := int(stepCount)*9 + 5
	if len(m.d) != expectedLength {
		return KeyboardMacroReport{}, fmt.Errorf("invalid length: %d, expected: %d", len(m.d), expectedLength)
	}

	steps := make([]KeyboardMacroStep, 0, int(stepCount))
	offset := 5
	for i := 0; i < int(stepCount); i++ {
		steps = append(steps, KeyboardMacroStep{
			Modifier: m.d[offset],
			Keys:     m.d[offset+1 : offset+7],
			Delay:    binary.BigEndian.Uint16(m.d[offset+7 : offset+9]),
		})

		offset += 1 + HidKeyBufferSize + 2
	}

	return KeyboardMacroReport{
		IsPaste:   isPaste,
		Steps:     steps,
		StepCount: stepCount,
	}, nil
}

// PointerReport ..
type PointerReport struct {
	X      int
	Y      int
	Button uint8
}

func toInt(b []byte) int {
	return int(b[0])<<24 + int(b[1])<<16 + int(b[2])<<8 + int(b[3])<<0
}

// PointerReport returns the point report from the message.
func (m *Message) PointerReport() (PointerReport, error) {
	if m.t != TypePointerReport {
		return PointerReport{}, fmt.Errorf("invalid message type: %d", m.t)
	}

	if len(m.d) != 9 {
		return PointerReport{}, fmt.Errorf("invalid message length: %d", len(m.d))
	}

	return PointerReport{
		X:      toInt(m.d[0:4]),
		Y:      toInt(m.d[4:8]),
		Button: uint8(m.d[8]),
	}, nil
}

// MouseReport ..
type MouseReport struct {
	DX     int8
	DY     int8
	Button uint8
}

// MouseReport returns the mouse report from the message.
func (m *Message) MouseReport() (MouseReport, error) {
	if m.t != TypeMouseReport {
		return MouseReport{}, fmt.Errorf("invalid message type: %d", m.t)
	}

	return MouseReport{
		DX:     int8(m.d[0]),
		DY:     int8(m.d[1]),
		Button: uint8(m.d[2]),
	}, nil
}

type KeyboardMacroState struct {
	State   bool
	IsPaste bool
}

// KeyboardMacroState returns the keyboard macro state report from the message.
func (m *Message) KeyboardMacroState() (KeyboardMacroState, error) {
	if m.t != TypeKeyboardMacroState {
		return KeyboardMacroState{}, fmt.Errorf("invalid message type: %d", m.t)
	}

	return KeyboardMacroState{
		State:   m.d[0] == uint8(1),
		IsPaste: m.d[1] == uint8(1),
	}, nil
}
