// rail.go
package rail

import (
	"bytes"
	"encoding/hex"

	"ShotRDP/grdp/core"
	"ShotRDP/grdp/glog"
	"ShotRDP/grdp/plugin"
)

const (
	ChannelName   = plugin.RAIL_SVC_CHANNEL_NAME
	ChannelOption = plugin.CHANNEL_OPTION_INITIALIZED | plugin.CHANNEL_OPTION_ENCRYPT_RDP |
		plugin.CHANNEL_OPTION_COMPRESS_RDP | plugin.CHANNEL_OPTION_SHOW_PROTOCOL
)

const (
	TS_RAIL_ORDER_EXEC                  = 0x0001
	TS_RAIL_ORDER_ACTIVATE              = 0x0002
	TS_RAIL_ORDER_SYSPARAM              = 0x0003
	TS_RAIL_ORDER_SYSCOMMAND            = 0x0004
	TS_RAIL_ORDER_HANDSHAKE             = 0x0005
	TS_RAIL_ORDER_NOTIFY_EVENT          = 0x0006
	TS_RAIL_ORDER_WINDOWMOVE            = 0x0008
	TS_RAIL_ORDER_LOCALMOVESIZE         = 0x0009
	TS_RAIL_ORDER_MINMAXINFO            = 0x000A
	TS_RAIL_ORDER_CLIENTSTATUS          = 0x000B
	TS_RAIL_ORDER_SYSMENU               = 0x000C
	TS_RAIL_ORDER_LANGBARINFO           = 0x000D
	TS_RAIL_ORDER_GET_APPID_REQ         = 0x000E
	TS_RAIL_ORDER_GET_APPID_RESP        = 0x000F
	TS_RAIL_ORDER_TASKBARINFO           = 0x0010
	TS_RAIL_ORDER_LANGUAGEIMEINFO       = 0x0011
	TS_RAIL_ORDER_COMPARTMENTINFO       = 0x0012
	TS_RAIL_ORDER_HANDSHAKE_EX          = 0x0013
	TS_RAIL_ORDER_ZORDER_SYNC           = 0x0014
	TS_RAIL_ORDER_CLOAK                 = 0x0015
	TS_RAIL_ORDER_POWER_DISPLAY_REQUEST = 0x0016
	TS_RAIL_ORDER_SNAP_ARRANGE          = 0x0017
	TS_RAIL_ORDER_GET_APPID_RESP_EX     = 0x0018
	TS_RAIL_ORDER_EXEC_RESULT           = 0x0080
)

type RailClient struct {
	w                        core.ChannelSender
	DesktopWidth             uint16
	DesktopHeight            uint16
	RemoteApplicationProgram string
	ShellWorkingDirectory    string
	RemoteApplicationCmdLine string
}

func NewClient() *RailClient {
	return &RailClient{
		DesktopWidth:             800,
		DesktopHeight:            600,
		RemoteApplicationProgram: "calc",
		ShellWorkingDirectory:    "/tmp",
	}
}

type RailPDUHeader struct {
	OrderType   uint16 `struc:"little"`
	OrderLength uint16 `struc:"little"`
}

func NewRailPDUHeader(mType, ln uint16) *RailPDUHeader {
	return &RailPDUHeader{
		OrderType:   mType,
		OrderLength: ln,
	}
}

func (h *RailPDUHeader) serialize() []byte {
	b := &bytes.Buffer{}
	core.WriteUInt16LE(h.OrderType, b)
	core.WriteUInt16LE(h.OrderLength, b)
	return b.Bytes()
}

func (c *RailClient) sendData(mType uint16, ln int, s []byte) {
	glog.Debug(ln, ":ln:", len(s), "data:", hex.EncodeToString(s))
	header := NewRailPDUHeader(mType, uint16(ln))

	b := &bytes.Buffer{}
	core.WriteBytes(header.serialize(), b)
	core.WriteBytes(s, b)

	c.Send(b.Bytes())
}

func (c *RailClient) Send(s []byte) (int, error) {
	glog.Debug("len:", len(s), "data:", hex.EncodeToString(s))
	name, _ := c.GetType()
	return c.w.SendToChannel(name, s)
}
func (c *RailClient) Sender(f core.ChannelSender) {
	c.w = f
}
func (c *RailClient) GetType() (string, uint32) {
	return ChannelName, ChannelOption
}

func (c *RailClient) Process(s []byte) {
	glog.Debug("recv:", hex.EncodeToString(s))
	r := bytes.NewReader(s)
	msgType, _ := core.ReadUint16LE(r)
	length, _ := core.ReadUint16LE(r)

	glog.Infof("rail: type=0x%x length=%d, all=%d", msgType, length, r.Len())

	b, _ := core.ReadBytes(int(length), r)
	glog.Info("b:", hex.EncodeToString(b))

	switch msgType {
	case TS_RAIL_ORDER_HANDSHAKE:
		glog.Info("TS_RAIL_ORDER_HANDSHAKE")
		c.processOrderHandshake(b)
	case TS_RAIL_ORDER_SYSPARAM:
		glog.Info("TS_RAIL_ORDER_SYSPARAM")
		c.processOrderSysparam(b)
	case TS_RAIL_ORDER_EXEC_RESULT:
		glog.Info("TS_RAIL_ORDER_EXEC_RESULT")
		c.processExecResult(b)

	default:
		glog.Errorf("type 0x%x not supported", msgType)
	}
}

func (c *RailClient) processOrderHandshake(b []byte) {
	r := bytes.NewReader(b)
	buildNumber, _ := core.ReadUInt32LE(r)
	glog.Info("buildNumber:", buildNumber)

	//send client info
	c.sendClientStatus()

	//send client systemparam
	c.sendClientSystemparam()

	//send client execute
	c.sendClientExecute()
}

const (
	TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE              = 0x00000001
	TS_RAIL_CLIENTSTATUS_AUTORECONNECT                   = 0x00000002
	TS_RAIL_CLIENTSTATUS_ZORDER_SYNC                     = 0x00000004
	TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED  = 0x00000010
	TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED        = 0x00000020
	TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED       = 0x00000040
	TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED = 0x00000080
	TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED = 0x00000100
	TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED   = 0x00000200
)

func (c *RailClient) sendClientStatus() {
	glog.Info("Send client Status")
	var flags uint32 = TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE

	//if (settings->AutoReconnectionEnabled)
	//clientStatus.flags |= TS_RAIL_CLIENTSTATUS_AUTORECONNECT;

	flags |= TS_RAIL_CLIENTSTATUS_ZORDER_SYNC
	flags |= TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED
	flags |= TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED
	flags |= TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED
	flags |= TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED

	b := &bytes.Buffer{}
	core.WriteUInt32LE(flags, b)

	c.sendData(TS_RAIL_ORDER_CLIENTSTATUS, 4, b.Bytes())
}

const (
	SPI_SET_SCREEN_SAVE_ACTIVE = 0x00000011
	SPI_SET_SCREEN_SAVE_SECURE = 0x00000077
)
const (
	/*Bit mask values for SPI_ parameters*/
	SPI_MASK_SET_DRAG_FULL_WINDOWS      = 0x00000001
	SPI_MASK_SET_KEYBOARD_CUES          = 0x00000002
	SPI_MASK_SET_KEYBOARD_PREF          = 0x00000004
	SPI_MASK_SET_MOUSE_BUTTON_SWAP      = 0x00000008
	SPI_MASK_SET_WORK_AREA              = 0x00000010
	SPI_MASK_DISPLAY_CHANGE             = 0x00000020
	SPI_MASK_TASKBAR_POS                = 0x00000040
	SPI_MASK_SET_HIGH_CONTRAST          = 0x00000080
	SPI_MASK_SET_SCREEN_SAVE_ACTIVE     = 0x00000100
	SPI_MASK_SET_SET_SCREEN_SAVE_SECURE = 0x00000200
	SPI_MASK_SET_CARET_WIDTH            = 0x00000400
	SPI_MASK_SET_STICKY_KEYS            = 0x00000800
	SPI_MASK_SET_TOGGLE_KEYS            = 0x00001000
	SPI_MASK_SET_FILTER_KEYS            = 0x00002000
)
const (
	SPI_SET_DRAG_FULL_WINDOWS = 0x00000025
	SPI_SET_KEYBOARD_CUES     = 0x0000100B
	SPI_SET_KEYBOARD_PREF     = 0x00000045
	SPI_SET_MOUSE_BUTTON_SWAP = 0x00000021
	SPI_SET_WORK_AREA         = 0x0000002F
	SPI_DISPLAY_CHANGE        = 0x0000F001
	SPI_TASKBAR_POS           = 0x0000F000
	SPI_SET_HIGH_CONTRAST     = 0x00000043
	SPI_SETCARETWIDTH         = 0x00002007
	SPI_SETSTICKYKEYS         = 0x0000003B
	SPI_SETTOGGLEKEYS         = 0x00000035
	SPI_SETFILTERKEYS         = 0x00000033
)

type TsFilterKeys struct {
	Flags      uint32
	WaitTime   uint32
	DelayTime  uint32
	RepeatTime uint32
	BounceTime uint32
}
type RailHighContrast struct {
	flags             uint32
	colorSchemeLength uint32
	colorScheme       string
}
type Rectangle16 struct {
	left   uint16
	top    uint16
	right  uint16
	bottom uint16
}
type RailSysparamOrder struct {
	param               uint32
	params              uint32
	dragFullWindows     uint8
	keyboardCues        uint8
	keyboardPref        uint8
	mouseButtonSwap     uint8
	workArea            Rectangle16
	displayChange       Rectangle16
	taskbarPos          Rectangle16
	highContrast        RailHighContrast
	caretWidth          uint32
	stickyKeys          uint32
	toggleKeys          uint32
	filterKeys          TsFilterKeys
	setScreenSaveActive uint8
	setScreenSaveSecure uint8
}

func (c *RailClient) sendClientSystemparam() {
	glog.Info("Send client Systemparam")

	var sp RailSysparamOrder
	sp.params = 0
	sp.params |= SPI_MASK_SET_HIGH_CONTRAST
	sp.highContrast.colorScheme = ""
	sp.highContrast.colorSchemeLength = 0
	sp.highContrast.flags = 0x7E
	sp.params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP
	sp.mouseButtonSwap = 0
	sp.params |= SPI_MASK_SET_KEYBOARD_PREF
	sp.keyboardPref = 0
	sp.params |= SPI_MASK_SET_DRAG_FULL_WINDOWS
	sp.dragFullWindows = 0
	sp.params |= SPI_MASK_SET_KEYBOARD_CUES
	sp.keyboardCues = 0
	sp.params |= SPI_MASK_SET_WORK_AREA
	sp.workArea.left = 0
	sp.workArea.top = 0
	sp.workArea.right = c.DesktopWidth
	sp.workArea.bottom = c.DesktopHeight

	if sp.params&SPI_MASK_SET_HIGH_CONTRAST != 0 {
		sp.param = SPI_SET_HIGH_CONTRAST
		c.sendOneClientSysparam(&sp)
	}

	if sp.params&SPI_MASK_TASKBAR_POS != 0 {
		sp.param = SPI_TASKBAR_POS
		c.sendOneClientSysparam(&sp)
	}

	if sp.params&SPI_MASK_SET_MOUSE_BUTTON_SWAP != 0 {
		sp.param = SPI_SET_MOUSE_BUTTON_SWAP
		c.sendOneClientSysparam(&sp)
	}

	if sp.params&SPI_MASK_SET_KEYBOARD_PREF != 0 {
		sp.param = SPI_SET_KEYBOARD_PREF
		c.sendOneClientSysparam(&sp)
	}

	if sp.params&SPI_MASK_SET_DRAG_FULL_WINDOWS != 0 {
		sp.param = SPI_SET_DRAG_FULL_WINDOWS
		c.sendOneClientSysparam(&sp)
	}

	if sp.params&SPI_MASK_SET_KEYBOARD_CUES != 0 {
		sp.param = SPI_SET_KEYBOARD_CUES
		c.sendOneClientSysparam(&sp)
	}

	if sp.params&SPI_MASK_SET_WORK_AREA != 0 {
		sp.param = SPI_SET_WORK_AREA
		glog.Debug("SPI_SET_WORK_AREA")
		c.sendOneClientSysparam(&sp)
	}
}

func (c *RailClient) sendOneClientSysparam(sp *RailSysparamOrder) {
	length := 0
	b := &bytes.Buffer{}
	core.WriteUInt32LE(sp.param, b)
	switch sp.param {
	case SPI_SET_DRAG_FULL_WINDOWS:
		core.WriteUInt8(sp.dragFullWindows, b)

	case SPI_SET_KEYBOARD_CUES:
		core.WriteUInt8(sp.keyboardCues, b)

	case SPI_SET_KEYBOARD_PREF:
		core.WriteUInt8(sp.keyboardPref, b)

	case SPI_SET_MOUSE_BUTTON_SWAP:
		core.WriteUInt8(sp.mouseButtonSwap, b)

	case SPI_SET_WORK_AREA:
		core.WriteUInt16LE(sp.workArea.left, b)
		core.WriteUInt16LE(sp.workArea.top, b)
		core.WriteUInt16LE(sp.workArea.right, b)
		core.WriteUInt16LE(sp.workArea.bottom, b)

	case SPI_DISPLAY_CHANGE:
		core.WriteUInt16LE(sp.displayChange.left, b)
		core.WriteUInt16LE(sp.displayChange.top, b)
		core.WriteUInt16LE(sp.displayChange.right, b)
		core.WriteUInt16LE(sp.displayChange.bottom, b)

	case SPI_TASKBAR_POS:
		core.WriteUInt16LE(sp.taskbarPos.left, b)
		core.WriteUInt16LE(sp.taskbarPos.top, b)
		core.WriteUInt16LE(sp.taskbarPos.right, b)
		core.WriteUInt16LE(sp.taskbarPos.bottom, b)

	case SPI_SET_HIGH_CONTRAST:
		core.WriteUInt32LE(sp.highContrast.flags, b)
		core.WriteUInt32LE(sp.highContrast.colorSchemeLength, b)
		data := core.UnicodeEncode(sp.highContrast.colorScheme)
		core.WriteBytes(data, b)

	case SPI_SETFILTERKEYS:
		core.WriteUInt32LE(sp.filterKeys.Flags, b)
		core.WriteUInt32LE(sp.filterKeys.WaitTime, b)
		core.WriteUInt32LE(sp.filterKeys.DelayTime, b)
		core.WriteUInt32LE(sp.filterKeys.RepeatTime, b)
		core.WriteUInt32LE(sp.filterKeys.BounceTime, b)

	case SPI_SETSTICKYKEYS:
		core.WriteUInt32LE(sp.stickyKeys, b)

	case SPI_SETCARETWIDTH:
		core.WriteUInt32LE(sp.caretWidth, b)

	case SPI_SETTOGGLEKEYS:
		core.WriteUInt32LE(sp.toggleKeys, b)

	case SPI_MASK_SET_SET_SCREEN_SAVE_SECURE:
		core.WriteUInt8(sp.setScreenSaveSecure, b)

	case SPI_MASK_SET_SCREEN_SAVE_ACTIVE:
		core.WriteUInt8(sp.setScreenSaveActive, b)

	default:
		glog.Error("ERROR_BAD_ARGUMENTS")
		return
	}

	c.sendData(TS_RAIL_ORDER_SYSPARAM, length+b.Len(), b.Bytes())
}

type RailExecOrder struct {
	flags                       uint16
	RemoteApplicationProgram    string
	RemoteApplicationWorkingDir string
	RemoteApplicationArguments  string
}

func (c *RailClient) sendClientExecute() {
	glog.Info("Send Client Execute")
	var exec RailExecOrder
	//exec.flags = TS_RAIL_EXEC_FLAG_EXPAND_ARGUMENTS
	exec.RemoteApplicationProgram = c.RemoteApplicationProgram
	exec.RemoteApplicationWorkingDir = c.ShellWorkingDirectory
	exec.RemoteApplicationArguments = c.RemoteApplicationCmdLine

	program := core.UnicodeEncode(exec.RemoteApplicationProgram)
	workdir := core.UnicodeEncode(exec.RemoteApplicationWorkingDir)
	arguments := core.UnicodeEncode(exec.RemoteApplicationArguments)

	length := 4
	b := &bytes.Buffer{}
	core.WriteUInt16LE(exec.flags, b)
	core.WriteUInt16LE(uint16(len(program)), b)
	core.WriteUInt16LE(uint16(len(workdir)), b)
	core.WriteUInt16LE(uint16(len(arguments)), b)
	core.WriteBytes(program, b)
	core.WriteBytes(workdir, b)
	core.WriteBytes(arguments, b)
	length += b.Len()

	c.sendData(TS_RAIL_ORDER_EXEC, length, b.Bytes())

}

func (c *RailClient) processOrderSysparam(b []byte) {
	r := bytes.NewReader(b)
	systemParam, _ := core.ReadUInt32LE(r)
	body, _ := core.ReadUInt8(r)
	glog.Infof("systemParam:0x%x, body:%d", systemParam, body)
}

const (
	//The Client Execute request was successful and the requested application or file has been launched.
	RAIL_EXEC_S_OK = 0x0000
	//The Client Execute request could not be satisfied because the server is not monitoring the current input desktop.
	RAIL_EXEC_E_HOOK_NOT_LOADED = 0x0001
	//The Execute request could not be satisfied because the request PDU was malformed.
	RAIL_EXEC_E_DECODE_FAILED = 0x0002
	//The Client Execute request could not be satisfied because the requested application was blocked by policy from being launched on the server.
	RAIL_EXEC_E_NOT_IN_ALLOWLIST = 0x0003
	//The Client Execute request could not be satisfied because the application or file path could not be found.
	RAIL_EXEC_E_FILE_NOT_FOUND = 0x0005
	//The Client Execute request could not be satisfied because an unspecified error occurred on the server.
	RAIL_EXEC_E_FAIL = 0x0006
	//The Client Execute request could not be satisfied because the remote session is locked.
	RAIL_EXEC_E_SESSION_LOCKED = 0x0007
)

func (c *RailClient) processExecResult(b []byte) {
	r := bytes.NewReader(b)
	flags, _ := core.ReadUint16LE(r)
	execResult, _ := core.ReadUint16LE(r)
	rawResult, _ := core.ReadUInt32LE(r)
	core.ReadUint16LE(r)
	exeOrFileLength, _ := core.ReadUint16LE(r)
	exeOrFile, _ := core.ReadBytes(r.Len(), r)
	glog.Info("flags:", flags, "execResult:", execResult, "rawResult:", rawResult)
	glog.Info("length:", exeOrFileLength, "file:", core.UnicodeDecode(exeOrFile))
}
