package server

// TODO rewrite to a better protocol with
//  - everything from the reader
//  - graphs

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	
	

	
	
	am 
	
	ss 
	
)

// ///// ///// /////

// ///// CLIENT

// ///// ///// /////

type Exportable struct {
	// TODO refac to MsgSchema
	// TODO version schemas
	MsgStruct *telemetry.DbgMsgStruct
	MsgTxs    []*telemetry.DbgMsgTx
}

type Client struct {
	// bits which get saved into the go file
	*Exportable

	// current transition, 1-based, mirrors the slider (eg 1 means tx.ID == 0)
	// TODO atomic
	// current step, 1-based, mirrors the slider
	// TODO atomic
	ReaderCollapsed bool
	Id              string
	MTimeSum        uint64
	SelectedGroup   string
	Connected       atomic.Bool
	ConnId          string
	SchemaHash      string
	// processed list of filtered tx _indexes_
	MsgTxsFiltered []int
	// cache of processed log entries
	LogMsgs [][]*am.LogEntry
	// indexes of txs with errors, desc order for bisects
	// TOOD refresh on GC
	Errors []int
	// processed
	MsgTxsParsed    []*types.MsgTxParsed
	MsgSchemaParsed *types.MsgSchemaParsed

	txCache   map[string]int
	txCacheMx sync.Mutex
}

func ( *Client) () {
	.txCache = nil
}

func ( *Client) () time.Time {
	if len(.MsgTxs) == 0 {
		return time.Time{}
	}

	return *.MsgTxs[len(.MsgTxs)-1].Time
}

func ( *Client) (,  int) bool {
	if slices.Contains(.Errors, ) {
		return true
	}

	// see TestHadErrSince
	 := sort.Search(len(.Errors), func( int) bool {
		return .Errors[] < 
	})

	if  >= len(.Errors) {
		return false
	}

	return -.Errors[] < 
}

func ( *Client) ( time.Time) int {
	 := len(.MsgTxs)
	if  == 0 {
		return -1
	}

	 := sort.Search(, func( int) bool {
		 := .MsgTxs[].Time
		return .After() || .Equal()
	})

	if  ==  {
		 =  - 1
	}
	 := .MsgTxs[]

	// pick the closer one
	if  > 0 && .Time.Sub() > .Sub(*.MsgTxs[-1].Time) {
		return 
	} else {
		return 
	}
}

func ( *Client) ( am.S) []int {
	return helpers.StatesToIndexes(.MsgStruct.StatesIndex, )
}

func ( *Client) ( []int) am.S {
	return helpers.IndexesToStates(.MsgStruct.StatesIndex, )
}

// TxIndex returns the index of transition ID [id] or -1 if not found.
func ( *Client) ( string) int {
	.txCacheMx.Lock()
	defer .txCacheMx.Unlock()

	if .txCache == nil {
		.txCache = make(map[string]int)
	}
	if ,  := .txCache[];  {
		return 
	}

	for ,  := range .MsgTxs {
		if .ID ==  {
			.txCache[] = 
			return 
		}
	}

	return -1
}

func ( *Client) ( int) *telemetry.DbgMsgTx {
	if  < 0 ||  >= len(.MsgTxs) {
		return nil
	}

	return .MsgTxs[]
}

func ( *Client) ( int) *types.MsgTxParsed {
	if  < 0 ||  >= len(.MsgTxsParsed) {
		return nil
	}

	return .MsgTxsParsed[]
}

func ( *Client) ( uint64) int {
	,  := slices.BinarySearchFunc(.MsgTxsParsed,
		&types.MsgTxParsed{TimeSum: }, func(,  *types.MsgTxParsed) int {
			if .TimeSum < .TimeSum {
				return -1
			} else if .TimeSum > .TimeSum {
				return 1
			}

			return 0
		})

	if ! {
		return 0
	}

	return 
}

func ( *Client) ( int) int {
	if  == 0 {
		return 0
	}

	return slices.Index(.MsgTxsFiltered, -1)
}

func ( *Client) () {
	// defaults
	 := .MsgStruct
	 := &types.MsgSchemaParsed{
		GroupsOrder: []string{"all"},
		Groups: map[string]am.S{
			"all": .StatesIndex,
		},
	}
	.MsgSchemaParsed = 

	if len(.GroupsOrder) == 0 {
		return
	}

	// schema groups
	 := false
	 := am.S{}
	for ,  := range .GroupsOrder {
		 := strings.TrimSuffix(, "StatesDef")
		if  == "self" {
			 = "- self"
		} else if  {
			 = "- " + 
		}
		.GroupsOrder = append(.GroupsOrder, )
		.Groups[] = .IndexesToStates(.Groups[])

		if  {
			// merge with prev groups
			.Groups[] = slices.Concat(.Groups[], )
			 = .Groups[]
		}

		if  == "self" {
			 = true
		}
	}
}

// TODO enable when needed
// func (c *Client) txByQueueTick(qTick uint64) int {
// 	idx, ok := slices.BinarySearchFunc(c.MsgTxs,
// 		&telemetry.DbgMsgTx{QueueTick: qTick},
// 		func(i, j *telemetry.DbgMsgTx) int {
// 			if i.QueueTick < j.QueueTick {
// 				return -1
// 			} else if i.QueueTick > j.QueueTick {
// 				return 1
// 			}
//
// 			return 0
// 		})
//
// 	if !ok {
// 		return 0
// 	}
//
// 	return idx
// }
//
// func (c *Client) txByMutQueueTick(qTick uint64) int {
// 	idx, ok := slices.BinarySearchFunc(c.MsgTxs,
// 		&telemetry.DbgMsgTx{MutQueueTick: qTick},
// 		func(i, j *telemetry.DbgMsgTx) int {
// 			if i.MutQueueTick < j.MutQueueTick {
// 				return -1
// 			} else if i.MutQueueTick > j.MutQueueTick {
// 				return 1
// 			}
//
// 			return 0
// 		})
//
// 	if !ok {
// 		return 0
// 	}
//
// 	return idx
// }

// TODO
// func NewClient()

// ///// ///// /////

// ///// SERVER

// ///// ///// /////

func (
	 *am.Machine,  string,  chan<- cmux.CMux,  []string,
	 bool,
) {
	// TODO dont listen in WASM

	var  error
	gob.Register(am.Relation(0))
	gob.Register(Exportable{})
	if  == "" {
		 = telemetry.DbgAddr
	}
	, ,  := net.SplitHostPort()
	,  := strconv.Atoi()
	,  := net.Listen("tcp", )
	if  != nil {
		log.Println()
		.AddErr(, nil)
		// TODO nice err msg
		panic()
	}
	.Log("dbg server started at %s", )

	// connect to another instances
	 := make([]*rpc.Client, len())
	for ,  := range  {
		[],  = rpc.Dial("tcp", )
		if  != nil {
			fmt.Printf("Cant fwd to %s: %s\n", , )
			os.Exit(1)
		}
	}

	// first cmux for tcp
	 := cmux.New()
	 := .Match(cmux.Any())
	go tcpAccept(, , )

	// second cmux for http/ws on port+1
	if  {
		 := fmt.Sprintf("%s:%d",
			[:strings.LastIndex(, ":")], +1)
		,  := net.Listen("tcp", )
		if  != nil {
			log.Println()
			.AddErr(, nil)
			panic()
		}
		 := cmux.New()
		 := .Match(cmux.HTTP1())
		 := &http.Server{
			Handler: http.HandlerFunc(func( http.ResponseWriter,  *http.Request) {

				if .RequestURI == "/diagrams/mach.ws" {
					wsHandler(, , )
					return
				}
				httpHandler(, , )
			})}

		go func() {
			.AddErr(.Serve(), nil)
		}()
		go func() {
			if  := .Serve();  != nil {
				fmt.Println()
				.AddErr(, nil)
				os.Exit(1)
			}
		}()
	}

	// push out for IoC
	if  != nil {
		go func() {
			time.Sleep(100 * time.Millisecond)
			 <- 
		}()
	}

	// start first cmux
	if  := .Serve();  != nil {
		fmt.Println()
		os.Exit(1)
	}
}

// TODO rename to RpcServer (compat break)
type RPCServer struct {
	Mach   *am.Machine
	ConnID string
	FwdTo  []*rpc.Client
}

type DbgMsgSchemaFwd struct {
	MsgStruct *telemetry.DbgMsgStruct
	ConnId    string
}

// TODO rename to RemoteMsgSchemaReceive (compat break)
func ( *RPCServer) (
	 *DbgMsgSchemaFwd,  *string,
) error {

	.Mach.Add1(ss.ConnectEvent, am.A{
		// TODO typed args
		"msg_struct": .MsgStruct,
		"conn_id":    .ConnId,
		"Client.id":  .MsgStruct.ID,
	})

	return nil
}

func ( *RPCServer) (
	 *telemetry.DbgMsgStruct,  *string,
) error {

	.Mach.Add1(ss.ConnectEvent, am.A{
		// TODO typed args
		"msg_struct": ,
		"conn_id":    .ConnID,
		"Client.id":  .ID,
	})

	// fwd to other instances
	for ,  := range .FwdTo {
		 := DbgMsgSchemaFwd{
			MsgStruct: ,
			ConnId:    .ConnID,
		}
		// TODO const name
		 := .Call("RPCServer.DbgMsgSchemaFwd", &, nil)
		if  != nil {
			return 
		}
	}

	return nil
}

var (
	queue       []*telemetry.DbgMsgTx
	queueConnId []string
	queueMx     sync.Mutex
	scheduled   bool
)

func ( *RPCServer) ( *telemetry.DbgMsgTx,  *string) error {
	queueMx.Lock()
	defer queueMx.Unlock()

	if !scheduled {
		scheduled = true
		go func() {
			// debounce
			time.Sleep(time.Second)

			queueMx.Lock()
			defer queueMx.Unlock()

			.Mach.Add1(ss.ClientMsg, am.A{
				"msgs_tx":  queue,
				"conn_ids": queueConnId,
			})
			// DEBUG
			// println("sent", len(queue), "msgs")
			queue = nil
			queueConnId = nil
			scheduled = false
		}()
	}

	 := time.Now()
	.Time = &
	queue = append(queue, )
	queueConnId = append(queueConnId, .ConnID)

	// fwd to other instances
	for ,  := range .FwdTo {
		 := DbgMsgTx{
			MsgTx:  ,
			ConnId: .ConnID,
		}
		// TODO const name
		 := .Call("RPCServer.DbgMsgTxFwd", , nil)
		if  != nil {
			return 
		}
	}

	return nil
}

type DbgMsgTx struct {
	MsgTx  *telemetry.DbgMsgTx
	ConnId string
}

// TODO rename to RemoteMsgTxReceive (compat break)
func ( *RPCServer) ( *DbgMsgTx,  *string) error {
	queueMx.Lock()
	defer queueMx.Unlock()

	 := .MsgTx
	 := .ConnId

	if !scheduled {
		scheduled = true
		go func() {
			// debounce
			time.Sleep(time.Second)

			queueMx.Lock()
			defer queueMx.Unlock()

			.Mach.Add1(ss.ClientMsg, am.A{
				"msgs_tx":  queue,
				"conn_ids": queueConnId,
			})
			// DEBUG
			// println("sent", len(queue), "msgs")
			queue = nil
			queueConnId = nil
			scheduled = false
		}()
	}

	 := time.Now()
	.Time = &
	queue = append(queue, )
	queueConnId = append(queueConnId, )

	// fwd to other instances
	for ,  := range .FwdTo {
		 := DbgMsgTx{
			MsgTx:  ,
			ConnId: ,
		}
		 := .Call("RPCServer.DbgMsgTxFwd", &, nil)
		if  != nil {
			return 
		}
	}

	return nil
}

func tcpAccept( net.Listener,  *am.Machine,  []*rpc.Client) {
	// TODO restart on error
	// defer mach.PanicToErr(nil)

	for {
		,  := .Accept()
		if  != nil {
			// log.Println(err)
			.AddErr(, nil)
			continue
		}

		// handle the client
		go func() {
			 := rpc.NewServer()
			 := utils.RandId(8)
			 := &RPCServer{
				Mach:   ,
				ConnID: ,
				FwdTo:  ,
			}

			 = .Register()
			if  != nil {
				.Mach.AddErr(, nil)
				// TODO err msg
				fmt.Println()
				os.Exit(1)
			}
			.ServeConn()

			// TODO pass to range fwdTo (dedicated RPC method)
			.Mach.Add1(ss.DisconnectEvent, am.A{"conn_id": })
		}()
	}
}

// ///// ///// /////

// ///// WEB

// ///// ///// /////

// httpHandler serves plain HTTP requests.
func httpHandler( http.ResponseWriter,  *http.Request,  *am.Machine) {
	 := make(chan struct{})

	middleware()

	if .Method == "OPTIONS" {
		.WriteHeader(http.StatusOK)
		return
	}

	.Add1(ss.WebReq, am.A{
		// TODO typed params
		"uri":                 .RequestURI,
		"*http.Request":       ,
		"http.ResponseWriter": ,
		"doneChan":            ,
		"addr":                .RemoteAddr,
	})
	// TODO timeout
	<-
}

// wsHandler handles WebSocket upgrade requests and echoes messages.
func wsHandler( http.ResponseWriter,  *http.Request,  *am.Machine) {
	middleware()

	,  := websocket.Accept(, , &websocket.AcceptOptions{
		InsecureSkipVerify: true,
	})
	if  != nil {
		.AddErrState(ss.ErrWeb, , nil)
		return
	}
	defer .Close(websocket.StatusInternalError, "internal error")
	 := make(chan struct{})
	.Add1(ss.WebSocket, am.A{
		// TODO typed params
		"*websocket.Conn":     ,
		"*http.Request":       ,
		"http.ResponseWriter": ,
		"doneChan":            ,
		"addr":                .RemoteAddr,
	})
	// TODO timeout
	<-
}

func middleware( http.ResponseWriter) {
	// CORS
	.Header().Set("Access-Control-Allow-Origin", "*")
	.Header().Set("Access-Control-Allow-Methods",
		"POST, GET, OPTIONS, PUT, DELETE")
	.Header().Set("Access-Control-Allow-Headers",
		"Accept, Content-Type, Content-Length, Accept-Encoding, "+
			"X-CSRF-Token, Authorization")

	// cache
	.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	.Header().Set("Pragma", "no-cache")
	.Header().Set("Expires", "0")
}

// ///// ///// /////

// ///// REMOTE MACHINE RPC

// TODO remove (used in dbg integration tests)
// ///// ///// /////

type GetField int

const (
	GetCursorTx GetField = iota + 1
	GetCursorStep
	GetClientCount
	GetMsgCount
	GetOpts
	GetSelectedState
)

func ( string) GetField {
	return GetField([0])
}

func ( GetField) () string {
	switch  {
	case GetCursorTx:
		return "GetCursorTx"
	case GetCursorStep:
		return "GetCursorStep"
	case GetClientCount:
		return "GetClientCount"
	case GetMsgCount:
		return "GetMsgCount"
	case GetOpts:
		return "GetOpts"
	case GetSelectedState:
		return "GetSelectedState"
	}

	return "!UNKNOWN!"
}

func ( GetField) () string {
	return string(rune())
}