// Copyright 2019-2022 Graham Clark. All rights reserved.  Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.

// Package ui contains user-interface functions and helpers for termshark.
package ui

import (
	"fmt"

	"github.com/gcla/gowid"
	"github.com/gcla/gowid/widgets/table"
	"github.com/gcla/termshark/v2/widgets/search"
)

//======================================================================

// ListResult represents a match for a search within the packet list view. This is really
// just the PSML already generated by tshark. The result is a packet number and a column;
// on a match, the packet list view will be updated to show the matched row and column.
type ListResult struct {
	Column    int
	PacketNum int
}

func (s ListResult) PacketNumber() int {
	return s.PacketNum
}

//======================================================================

// Search in the packet list view:
// 1    0.000000 10.44.10.228 8.8.4.4      DNS    77      Standard query 0x51fe A ac.duckduckgo.com
// 2    0.024306 10.44.10.228 176.103.130. DNS    77      Standard query 0x51fe A ac.duckduckgo.com
// 3    0.024875 8.8.4.4      10.44.10.228 DNS    139     Standard query response 0x51fe A ac.duckduckgo.com CNAME
// 4    0.025500 10.44.10.228 184.72.104.1 TCP    66      55408 → 443 [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 S 0
type ListSearchCallbacks struct {
	*commonSearchCallbacks
	*SearchStopper
	samePacket bool // set to false if we have advanced beyond the current packet, so don't need to check list pos
	search     chan search.IntermediateResult
}

var _ search.IRequestStop = (*ListSearchCallbacks)(nil)
var _ search.ICallbacks = (*ListSearchCallbacks)(nil)

func (w *ListSearchCallbacks) Reset(app gowid.IApp) {
	w.SearchStopper.Requested = false
	w.ticks = 0
}

func (w *ListSearchCallbacks) StartingPosition() (interface{}, error) {
	p, err := packetNumberFromCurrentTableRow()
	if err != nil {
		return ListResult{}, err
	}

	if packetListView == nil {
		return ListResult{}, fmt.Errorf("No packets loaded")
	}

	coords, err := packetListView.FocusXY()
	if err != nil {
		return ListResult{}, err
	}

	return ListResult{
		PacketNum: p.Pos,
		Column:    coords.Column + 1,
	}, nil
}

// own goroutine
// startPacketNumber >= 1
func (w *ListSearchCallbacks) SearchPacketsFrom(ifrom interface{}, istart interface{}, term search.INeedle, app gowid.IApp) {

	start := istart.(ListResult)
	from := ifrom.(ListResult)

	res := search.Result{}

	// True if we have packets in the current batch to search (and we aren't blocked waiting for them to load)
	curPacketNumber := from.PacketNum
	column := from.Column

	var resumeAt *ListResult

	defer func() {
		if resumeAt != nil {
			w.search <- search.IntermediateResult{
				Res:      res,
				ResumeAt: *resumeAt,
			}
		} else {
			w.search <- search.IntermediateResult{
				Res: res,
			}
		}
	}()

Loop:
	for {
		// Map from packet number to index in pdml array
		w.DoIfStopped(func() {
			res.Interrupted = true
		})
		if res.Interrupted {
			break Loop
		}

		Loader.PsmlLoader.Lock()
		// curPacketNumber is the packet number from the pdml <packet>24</packet>. Remember there might
		// be a display filter in place.
		packetIndex, ok := Loader.PacketNumberMap[curPacketNumber]
		if !ok {
			// 1-based - packet number e.g. <packet>24</packet>
			resumeAt = &ListResult{
				PacketNum: curPacketNumber,
			}
			Loader.PsmlLoader.Unlock()
			break
		}

		if packetIndex >= len(Loader.PsmlData()) {
			panic(nil)
		}

		datas := Loader.PsmlData()[packetIndex]

		Loader.PsmlLoader.Unlock()

		if column > len(datas) {
			column = len(datas)
		}

		for j, data := range datas[column:len(datas)] {
			mpos := term.Search(data)
			if mpos != -1 {
				coords := ListResult{
					Column:    j + column,
					PacketNum: curPacketNumber,
				}
				res.Position = coords
				res.Success = true
				// Terminate the search
				break Loop
			}
		}

		column = 0
		w.samePacket = false

		// Can this be more sophisticated?
		Loader.PsmlLoader.Lock()
		// 32, 44, 45, 134, 209,...
		curPacketNumber, ok = Loader.PacketNumberOrder[curPacketNumber]
		if !ok {
			curPacketNumber = Loader.PacketNumberOrder[0]
		}
		Loader.PsmlLoader.Unlock()

		if curPacketNumber == start.PacketNum {
			break Loop
		}
	}
}

func (s *ListSearchCallbacks) SearchPacketsResult(res search.Result, app gowid.IApp) {
	app.Run(gowid.RunFunction(func(app gowid.IApp) {
		// Do this because we might be on the same listview packet, so the change callback
		// won't run and adjust the lower view
		//
		// UPDATE - this assumes the original start position in the table is the same as
		// the one now.
		ClearProgressWidgetFor(app, SearchOwns)

		if res.Interrupted {
			return
		}

		if !res.Success {
			OpenError("Not found.", app)
			return
		}

		coords := res.Position.(ListResult)

		trow, err := tableRowFromPacketNumber(coords.PacketNum)
		if err != nil {
			OpenError(fmt.Sprintf("Could not move to packet %d\n\n%v", coords.PacketNum, err), app)
			return
		}

		packetListView.SetFocusXY(app, table.Coords{Column: coords.Column, Row: trow})
		// Don't continue to jump to the end
		AutoScroll = false

		// Callback might not run if focus position in table is the same e.g. if we find a match
		// on the same row that we started. So in that case, to expand the lower widgets, call
		// setLowerWidgets explicitly - don't rely on the focus-changed callback. And I can't
		// do a shortcut and call this if start == current because the starting position for the
		// search may not be the same as the list-view row on display - maybe the search has
		// resumed not that some extra PDML data has been loaded
		setLowerWidgets(app)

		// It looks better than having the found packet be at the top of the view
		packetListView.GoToMiddle(app)
		curPacketStructWidget.GoToMiddle(app)
		curStructWidgetState = curPacketStructWidget.State()
	}))
}

//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
