package steward

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"time"

	"gopkg.in/yaml.v3"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

// ---------------------------------------------------------------------
// Main structure
// ---------------------------------------------------------------------

// tui holds general types used within the tui.
type tui struct {
	toConsoleCh    chan []byte
	toRingbufferCh chan []subjectAndMessage
	ctx            context.Context
	nodeName       Node
}

// newTui returns a new tui.
func newTui(nodeName Node) (*tui, error) {
	ch := make(chan []byte)
	s := tui{
		toConsoleCh: ch,
		nodeName:    nodeName,
	}
	return &s, nil
}

// slide holds the information about a slide
type slide struct {
	name      string
	key       tcell.Key
	primitive tview.Primitive
}

// Start will start the tui.
func (t *tui) Start(ctx context.Context, toRingBufferCh chan []subjectAndMessage) error {
	t.ctx = ctx
	t.toRingbufferCh = toRingBufferCh

	pages := tview.NewPages()

	app := tview.NewApplication()

	// Check if F key is pressed, and switch slide accordingly.
	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyF1:
			pages.SwitchToPage("console")
			return nil
		case tcell.KeyF2:
			pages.SwitchToPage("message")
			return nil
		case tcell.KeyF3:
			pages.SwitchToPage("info")
			return nil
		case tcell.KeyCtrlC:
			app.Stop()
			log.Printf("info: detected ctrl+c, stopping TUI\n")
		}
		return event
	})

	info := tview.NewTextView().
		SetDynamicColors(true).
		SetRegions(true).
		SetWrap(false)

	// The slides to draw, and their name.
	// NB: This slice is being looped over further below, to create the menu
	// elements. If adding a new slide, make sure that slides are ordered in
	// chronological order, so we can auto generate the info menu with it's
	// corresponding F key based on the slice index+1.
	slides := []slide{
		{name: "console", key: tcell.KeyF1, primitive: t.console(app)},
		{name: "message", key: tcell.KeyF2, primitive: t.messageSlide(app)},
		{name: "info", key: tcell.KeyF3, primitive: t.infoSlide(app)},
	}

	// Add a page for each slide.
	for i, v := range slides {
		if i == 0 {
			pages.AddPage(v.name, v.primitive, true, true)
			fmt.Fprintf(info, " F%v:%v  ", i+1, v.name)
			continue
		}

		pages.AddPage(v.name, v.primitive, true, false)
		fmt.Fprintf(info, " F%v:%v  ", i+1, v.name)
	}

	// Create the main layout.
	layout := tview.NewFlex()
	//layout.SetBorder(true)
	layout.SetDirection(tview.FlexRow).
		AddItem(pages, 0, 10, true).
		AddItem(info, 1, 1, false)

	root := app.SetRoot(layout, true)
	root.EnableMouse(true)

	if err := root.Run(); err != nil {
		log.Printf("error: root.Run(): %v\n", err)
		os.Exit(1)
	}

	return nil
}

// ---------------------------------------------------------------------
// Slides
// ---------------------------------------------------------------------

func (t *tui) infoSlide(app *tview.Application) tview.Primitive {
	flex := tview.NewFlex()
	flex.SetTitle("info")
	flex.SetBorder(true)

	textView := tview.NewTextView()
	flex.AddItem(textView, 0, 1, false)

	fmt.Fprintf(textView, "Information page for Steward TUI.\n")

	return flex
}

// drawMessageInputFields
//
// Draw all the message input field with values on the screen.
//
// Loop trough all the fields of the Message struct, and create
// a an input field or dropdown selector for each field.
// If a field of the struct is not defined below, it will be
// created a "no defenition" element, so it we can easily spot
// Message fields who miss an item in the form.
//
// INFO: The reason that reflect are being used here is to have
// a simple way of detecting that we are creating form fields
// for all the fields in the struct. If we have forgot'en one
// it will create a "no case" field in the console, to easily
// detect that a struct field are missing a defenition below.
func drawMessageInputFields(p slideMessageEdit, m tuiMessage) {
	fieldWidth := 0

	mRefVal := reflect.ValueOf(m)

	for i := 0; i < mRefVal.NumField(); i++ {

		fieldName := mRefVal.Type().Field(i).Name

		switch fieldName {
		case "_":
		case "ToNode":
			// Get nodes from file.
			values, err := getNodeNames("nodeslist.cfg")
			if err != nil {
				log.Printf("error: unable to open file: %v\n", err)
			}

			if m.ToNode != nil && *m.ToNode != "" {
				tmp := []string{string(*m.ToNode)}
				tmp = append(tmp, values...)
				values = tmp
			}

			p.inputForm.AddDropDown(fieldName, values, 0, nil).SetItemPadding(1)
			//c.msgForm.AddDropDown(mRefVal.Type().Field(i).Name, values, 0, nil).SetItemPadding(1)
		case "ToNodes":
			if m.ToNodes == nil {
				p.inputForm.AddInputField(fieldName, "", fieldWidth, nil, nil)
				continue
			}

			if len(*m.ToNodes) != 0 {
				val1 := *m.ToNodes
				var val2 string
				for i, v := range val1 {
					if i == 0 {
						val2 = fmt.Sprintf("\"%v\"", v)
						continue
					}

					val2 = fmt.Sprintf("%v,\"%v\"", val2, v)
				}

				p.inputForm.AddInputField(fieldName, val2, fieldWidth, nil, nil)
			}

		case "Method":
			var v Method
			ma := v.GetMethodsAvailable()
			values := []string{}
			for k := range ma.Methodhandlers {
				values = append(values, string(k))
			}

			if m.Method != nil && *m.Method != "" {
				tmp := []string{string(*m.Method)}
				tmp = append(tmp, values...)
				values = tmp
			}

			p.inputForm.AddDropDown(fieldName, values, 0, nil).SetItemPadding(1)
		case "MethodArgs":
			if m.MethodArgs == nil {
				p.inputForm.AddInputField(fieldName, "", 0, nil, nil)
				continue
			}

			if len(*m.MethodArgs) != 0 {
				val1 := *m.MethodArgs
				var val2 string
				for i, v := range val1 {
					if i == 0 {
						val2 = fmt.Sprintf("\"%v\"", v)
						continue
					}

					val2 = fmt.Sprintf("%v,\"%v\"", val2, v)
				}

				p.inputForm.AddInputField(fieldName, val2, 0, nil, nil)
			}

		case "ReplyMethod":
			var v Method
			rm := v.GetReplyMethods()
			values := []string{}
			for _, k := range rm {
				values = append(values, string(k))
			}

			if m.ReplyMethod != nil && *m.ReplyMethod != "" {
				tmp := []string{string(*m.ReplyMethod)}
				tmp = append(tmp, values...)
				values = tmp
			}

			p.inputForm.AddDropDown(fieldName, values, 0, nil).SetItemPadding(1)
		case "ReplyMethodArgs":
			if m.ReplyMethodArgs == nil {
				p.inputForm.AddInputField(fieldName, "", fieldWidth, nil, nil)
				continue
			}

			if len(*m.ReplyMethodArgs) != 0 {
				val1 := *m.ReplyMethodArgs
				var val2 string
				for i, v := range val1 {
					if i == 0 {
						val2 = fmt.Sprintf("\"%v\"", v)
						continue
					}

					val2 = fmt.Sprintf("%v,\"%v\"", val2, v)
				}

				p.inputForm.AddInputField(fieldName, val2, fieldWidth, nil, nil)
			}
		case "ACKTimeout":
			value := 30

			if m.ACKTimeout != nil && *m.ACKTimeout != 0 {
				value = *m.ACKTimeout
			}

			p.inputForm.AddInputField(fieldName, fmt.Sprintf("%d", value), fieldWidth, validateInteger, nil)
		case "Retries":
			value := 1

			if m.Retries != nil && *m.Retries != 0 {
				value = *m.Retries
			}

			p.inputForm.AddInputField(fieldName, fmt.Sprintf("%d", value), fieldWidth, validateInteger, nil)
		case "ReplyACKTimeout":
			value := 30

			if m.ReplyACKTimeout != nil && *m.ReplyACKTimeout != 0 {
				value = *m.ReplyACKTimeout
			}

			p.inputForm.AddInputField(fieldName, fmt.Sprintf("%d", value), fieldWidth, validateInteger, nil)
		case "ReplyRetries":
			value := 1

			if m.ReplyRetries != nil && *m.ReplyRetries != 0 {
				value = *m.ReplyRetries
			}

			p.inputForm.AddInputField(fieldName, fmt.Sprintf("%d", value), fieldWidth, validateInteger, nil)
		case "MethodTimeout":
			value := 120

			if m.MethodTimeout != nil && *m.MethodTimeout != 0 {
				value = *m.MethodTimeout
			}

			p.inputForm.AddInputField(fieldName, fmt.Sprintf("%d", value), fieldWidth, validateInteger, nil)
		case "ReplyMethodTimeout":
			value := 120

			if m.ReplyMethodTimeout != nil && *m.ReplyMethodTimeout != 0 {
				value = *m.ReplyMethodTimeout
			}

			p.inputForm.AddInputField(fieldName, fmt.Sprintf("%d", value), fieldWidth, validateInteger, nil)
		case "Directory":
			value := "/some-dir/"

			if m.Directory != nil && *m.Directory != "" {
				value = *m.Directory
			}

			p.inputForm.AddInputField(fieldName, value, fieldWidth, nil, nil)
		case "FileName":
			value := ".log"

			if m.FileName != nil && *m.FileName != "" {
				value = *m.FileName
			}

			p.inputForm.AddInputField(fieldName, value, fieldWidth, nil, nil)

		default:
			// Add a no definition fields to the form if a a field within the
			// struct were missing an action above, so we can easily detect
			// if there is missing a case action for one of the struct fields.

			p.inputForm.AddDropDown("error: no case for: "+fieldName, []string{"1", "2"}, 0, nil).SetItemPadding(1)
		}

	}

}

// pageMessage is a struct for holding all the main forms and
// views used in the message slide, so we can easily reference
// them later in the code.
type slideMessageEdit struct {
	flex          *tview.Flex
	selectMessage *tview.Form
	inputForm     *tview.Form
	outputForm    *tview.TextView
	logForm       *tview.TextView
	saveForm      *tview.Form
}

// messageSlide is the main function for setting up the slides.
func (t *tui) messageSlide(app *tview.Application) tview.Primitive {

	p := slideMessageEdit{}

	p.selectMessage = tview.NewForm()
	p.selectMessage.SetBorder(true).SetTitle("Select Message").SetTitleAlign(tview.AlignLeft)

	p.inputForm = tview.NewForm()
	p.inputForm.SetBorder(true).SetTitle("Message input").SetTitleAlign(tview.AlignLeft)

	p.outputForm = tview.NewTextView()
	p.outputForm.SetBorder(true).SetTitle("Message output").SetTitleAlign(tview.AlignLeft)
	p.outputForm.SetChangedFunc(func() {
		// Will cause the log window to be redrawn as soon as
		// new output are detected.
		app.Draw()
	})

	p.logForm = tview.NewTextView()
	p.logForm.SetBorder(true).SetTitle("Log/Status").SetTitleAlign(tview.AlignLeft)
	p.logForm.SetChangedFunc(func() {
		// Will cause the log window to be redrawn as soon as
		// new output are detected.
		app.Draw()
	})

	p.saveForm = tview.NewForm()
	p.saveForm.SetBorder(true).SetTitle("Save message").SetTitleAlign(tview.AlignLeft)

	// Create a flex layout.
	//
	// Create the outer flex layout.
	p.flex = tview.NewFlex().SetDirection(tview.FlexRow).
		// Add a flex for the top windows with columns.
		AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
			// Add a new flex for splitting output form horizontally.
			AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
				// Add the select message form.
				AddItem(p.selectMessage, 5, 0, false).
				// Add the input message form.
				AddItem(p.inputForm, 0, 10, false),
				0, 10, false).
			// Add a new flex for splitting output form horizontally.
			AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
				// Add the message output form.
				AddItem(p.outputForm, 0, 10, false).
				// Add the save message form.
				AddItem(p.saveForm, 0, 2, false),
				0, 10, false),
			0, 10, false).
		// Add a flex for the bottom log window.
		AddItem(tview.NewFlex().
			// Add the log form.
			AddItem(p.logForm, 0, 1, false),
			0, 1, false)

	m := tuiMessage{}

	// ---

	// Add a dropdown menu to select message files to use.

	msgsValues := t.getMessageNames(p.logForm)

	msgDropdownFunc := func(msgFileName string, index int) {
		filePath := filepath.Join("messages", msgFileName)
		fh, err := os.Open(filePath)
		if err != nil {
			fmt.Fprintf(p.logForm, "error: failed to open message file: %v\n", err)
			return
		}
		defer fh.Close()

		fileContent, err := io.ReadAll(fh)
		if err != nil {
			fmt.Fprintf(p.logForm, "error: failed to read message file: %v\n", err)
			return
		}

		var msgs []tuiMessage

		err = yaml.Unmarshal(fileContent, &msgs)
		if err != nil {
			fmt.Fprintf(p.logForm, "error: yaml unmarshal of file content failed: %v\n", err)
			return
		}

		m = msgs[0]

		// Clear the form.
		p.inputForm.Clear(false)

		// Draw all the message input field with values on the screen.
		p.inputForm.Clear(false)
		drawMessageInputFields(p, m)
	}

	messageDropdown := tview.NewDropDown()
	messageDropdown.SetLabelColor(tcell.ColorIndianRed)
	messageDropdown.SetLabel("message").SetOptions(msgsValues, msgDropdownFunc)
	// Clear the form.
	p.inputForm.Clear(false)
	drawMessageInputFields(p, m)
	p.selectMessage.AddFormItem(messageDropdown)

	p.inputForm.AddButton("update message dropdown menu", func() {
		messageMessageValues := t.getMessageNames(p.logForm)
		messageDropdown.SetLabel("message").SetOptions(messageMessageValues, msgDropdownFunc)
	})

	// ---

	// Variable to hold the last output created when the generate button have
	// been pushed.
	var lastGeneratedMessage []byte
	var saveFileName string

	// Add Buttons below the message fields. Like Generate and Exit.
	p.inputForm.
		// Add a generate button, which when pressed will loop through all the
		// message form items, and if found fill the value into a msg struct,
		// and at last write it to a file.
		AddButton("generate to console", func() {
			p.outputForm.Clear()

			m := tuiMessage{}
			// Loop trough all the form fields, check the value of each
			// form field, and add the value to m.
			for i := 0; i < p.inputForm.GetFormItemCount(); i++ {
				fi := p.inputForm.GetFormItem(i)
				label, value := getLabelAndValue(fi)

				switch label {
				case "message":
				case "ToNode":
					v := Node(value)
					m.ToNode = &v
				case "ToNodes":
					slice, err := stringToNode(value)
					if err != nil {
						fmt.Fprintf(p.logForm, "%v : error: ToNodes missing or malformed format, should be \"arg0\",\"arg1\",\"arg2\", %v\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), err)
						return
					}

					m.ToNodes = slice
				case "Method":
					v := Method(value)
					m.Method = &v
				case "MethodArgs":
					slice, err := stringToSlice(value)
					if err != nil {
						fmt.Fprintf(p.logForm, "%v : error: MethodArgs missing or malformed format, should be \"arg0\",\"arg1\",\"arg2\", %v\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), err)
						return
					}

					m.MethodArgs = slice
				case "ReplyMethod":
					v := Method(value)
					m.ReplyMethod = &v
				case "ReplyMethodArgs":
					slice, err := stringToSlice(value)
					if err != nil {
						fmt.Fprintf(p.logForm, "%v : error: ReplyMethodArgs missing or malformed format, should be \"arg0\",\"arg1\",\"arg2\", %v\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), err)
						return
					}

					m.ReplyMethodArgs = slice
				case "ACKTimeout":
					v, _ := strconv.Atoi(value)
					m.ACKTimeout = &v
				case "Retries":
					v, _ := strconv.Atoi(value)
					m.Retries = &v
				case "ReplyACKTimeout":
					v, _ := strconv.Atoi(value)
					m.ReplyACKTimeout = &v
				case "ReplyRetries":
					v, _ := strconv.Atoi(value)
					m.ReplyRetries = &v
				case "MethodTimeout":
					v, _ := strconv.Atoi(value)
					m.MethodTimeout = &v
				case "ReplyMethodTimeout":
					v, _ := strconv.Atoi(value)
					m.ReplyMethodTimeout = &v
				case "Directory":
					m.Directory = &value
				case "FileName":
					m.FileName = &value

				default:
					fmt.Fprintf(p.logForm, "%v : error: did not find case definition for how to handle the \"%v\" within the switch statement\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), label)
					return
				}
			}
			msgs := []tuiMessage{}
			msgs = append(msgs, m)

			// // msgsIndented, err := json.MarshalIndent(msgs, "", "    ")
			// buf := new(bytes.Buffer)
			// enc := json.NewEncoder(buf)
			// enc.SetEscapeHTML(false)
			// enc.SetIndent("", "    ")
			// err := enc.Encode(msgs)
			// if err != nil {
			// 	fmt.Fprintf(p.logForm, "%v : error: jsonIndent failed: %v\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), err)
			// }

			bs, err := yaml.Marshal(msgs)
			if err != nil {
				fmt.Fprintf(p.logForm, "%v : error: yaml marshal failed: %v\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), err)
			}

			// Copy the message to a variable outside this scope so we can use
			// the content for example if we want to save the message to file.
			lastGeneratedMessage = bs

			_, err = p.outputForm.Write(bs)
			if err != nil {
				fmt.Fprintf(p.logForm, "%v : error: write to fh failed: %v\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), err)
			}
		}).

		// Add exit button.
		AddButton("exit", func() {
			app.Stop()
		})

	// app.SetFocus(p.inputForm)

	p.saveForm.
		AddInputField("FileName", "", 40, nil, func(text string) {
			saveFileName = text
		}).
		AddButton("save", func() {
			messageFolder := "messages"

			if saveFileName == "" {
				fmt.Fprintf(p.logForm, "error: missing filename\n")
				return
			}

			if _, err := os.Stat(messageFolder); os.IsNotExist(err) {
				err := os.MkdirAll(messageFolder, 0770)
				if err != nil {
					fmt.Fprintf(p.logForm, "error: failed to create messages folder: %v\n", err)
					return
				}
			}

			file := filepath.Join(messageFolder, saveFileName)
			fh, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755)
			if err != nil {
				fmt.Fprintf(p.logForm, "error: opening file for writing: %v\n", err)
				return
			}
			defer fh.Close()

			_, err = fh.Write([]byte(lastGeneratedMessage))
			if err != nil {
				fmt.Fprintf(p.logForm, "error: writing message to file: %v\n", err)
				return
			}

			fmt.Fprintf(p.logForm, "info: succesfully wrote message to file: %v\n", file)

			// update the select message dropdown
			messageMessageValues := t.getMessageNames(p.logForm)
			messageDropdown.SetLabel("message").SetOptions(messageMessageValues, msgDropdownFunc)
			// p.inputForm.Clear(false)

		})

	return p.flex
}

func (t *tui) console(app *tview.Application) tview.Primitive {

	// pageMessage is a struct for holding all the main forms and
	// views used in the message slide, so we can easily reference
	// them later in the code.
	type slideConsole struct {
		flex       *tview.Flex
		selectForm *tview.Form
		outputForm *tview.TextView
	}

	p := slideConsole{}

	p.selectForm = tview.NewForm()
	p.selectForm.SetBorder(true).SetTitle("select").SetTitleAlign(tview.AlignLeft)
	p.selectForm.SetButtonsAlign(tview.AlignCenter)
	p.selectForm.SetHorizontal(false)

	p.outputForm = tview.NewTextView()
	p.outputForm.SetBorder(true).SetTitle("output").SetTitleAlign(tview.AlignLeft)
	p.outputForm.SetChangedFunc(func() {
		// Will cause the log window to be redrawn as soon as
		// new output are detected.
		app.Draw()
	})

	// Create a flex layout.
	//
	// Create the outer flex layout.
	p.flex = tview.NewFlex().SetDirection(tview.FlexRow).
		// Add a flex for the top windows with columns.
		AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
			AddItem(p.selectForm, 0, 3, false).

			// Add the message output form.
			AddItem(p.outputForm, 0, 10, false),

			0, 10, false)
		// Add a flex for the bottom log window.

	// Add items.

	// Create nodes dropdown field.
	nodesList, err := getNodeNames("nodeslist.cfg")
	if err != nil {
		fmt.Fprintf(p.outputForm, "error: failed to open nodeslist.cfg file\n")
	}

	nodesDropdown := tview.NewDropDown()
	nodesDropdown.SetLabelColor(tcell.ColorIndianRed)
	nodesDropdown.SetLabel("nodes").SetOptions(nodesList, nil)
	p.selectForm.AddFormItem(nodesDropdown)

	msgsValues := t.getMessageNames(p.outputForm)

	messageDropdown := tview.NewDropDown()
	messageDropdown.SetLabelColor(tcell.ColorIndianRed)
	messageDropdown.SetLabel("message").SetOptions(msgsValues, nil)
	p.selectForm.AddFormItem(messageDropdown)

	// Add button for manually updating dropdown menus.
	p.selectForm.AddButton("update", func() {
		nodesList, err := getNodeNames("nodeslist.cfg")
		if err != nil {
			fmt.Fprintf(p.outputForm, "error: failed to open nodeslist.cfg file\n")
		}
		nodesDropdown.SetLabel("nodes").SetOptions(nodesList, nil)

		msgsValues := t.getMessageNames(p.outputForm)
		messageDropdown.SetLabel("message").SetOptions(msgsValues, nil)
	})

	// Add button for clearing the output form.
	p.selectForm.AddButton("clear", func() {
		p.outputForm.Clear()
	})

	// Update the dropdown menus when the flex view gets focus.
	p.flex.SetFocusFunc(func() {
		nodesList, err := getNodeNames("nodeslist.cfg")
		if err != nil {
			fmt.Fprintf(p.outputForm, "error: failed to open nodeslist.cfg file\n")
		}
		nodesDropdown.SetLabel("nodes").SetOptions(nodesList, nil)

		messageValues := t.getMessageNames(p.outputForm)
		messageDropdown.SetLabel("message").SetOptions(messageValues, nil)
	})

	p.selectForm.AddButton("send message", func() {
		// here........

		nr, msgFileName := messageDropdown.GetCurrentOption()
		if nr < 1 {
			fmt.Fprintf(p.outputForm, "info: please select a message from the dropdown: %v\n", msgFileName)
			return
		}

		nr, toNode := nodesDropdown.GetCurrentOption()
		if nr < 1 {
			fmt.Fprintf(p.outputForm, "info: please select a message from the dropdown: %v\n", msgFileName)
			return
		}

		filePath := filepath.Join("messages", msgFileName)
		fh, err := os.Open(filePath)
		if err != nil {
			fmt.Fprintf(p.outputForm, "error: failed to open message file: %v\n", err)
			return
		}
		defer fh.Close()

		fileContent, err := io.ReadAll(fh)
		if err != nil {
			fmt.Fprintf(p.outputForm, "error: failed to read message file: %v\n", err)
			return
		}

		var msgs []Message
		err = yaml.Unmarshal(fileContent, &msgs)
		if err != nil {
			fmt.Fprintf(p.outputForm, "error: yaml unmarshal of file content failed: %v\n", err)
			return
		}

		msg := msgs[0]
		msg.FromNode = t.nodeName
		msg.ToNode = Node(toNode)

		// fmt.Fprintf(p.outputForm, "%#v\n", msg)

		sam, err := newSubjectAndMessage(msg)
		if err != nil {
			fmt.Fprintf(p.outputForm, "error: newSubjectAndMessage failed: %v\n", err)
			return
		}

		sams := []subjectAndMessage{sam}

		t.toRingbufferCh <- sams

	})

	go func() {
		for {
			select {
			case messageData := <-t.toConsoleCh:
				for _, v := range messageData {
					fmt.Fprintf(p.outputForm, "%v", string(v))
				}
			case <-t.ctx.Done():
				log.Printf("info: stopped tui toConsole worker\n")
				return
			}
		}
	}()

	return p.flex
}

// ---------------------------------------------------------------------
// Helper functions
// ---------------------------------------------------------------------

// getMessageNames will get the names of all the messages in
// the messages folder.
func (t *tui) getMessageNames(outputForm *tview.TextView) []string {
	// Create messages dropdown field.
	fInfo, err := os.ReadDir("messages")
	if err != nil {
		fmt.Fprintf(outputForm, "error: failed to read files from messages dir\n")
	}

	msgsValues := []string{}
	msgsValues = append(msgsValues, "")

	for _, v := range fInfo {
		msgsValues = append(msgsValues, v.Name())
	}

	return msgsValues
}

// stringToSlice will Split the comma separated string
// into a and remove the start and end ampersand.
func stringToSlice(s string) (*[]string, error) {
	if s == "" {
		return nil, nil
	}

	var stringSlice []string

	sp := strings.Split(s, ",")

	for _, v := range sp {
		// Check if format is correct, return if not.
		pre := strings.HasPrefix(v, "\"")
		suf := strings.HasSuffix(v, "\"")
		if !pre || !suf {
			return nil, fmt.Errorf("stringToSlice: missing leading or ending ampersand")
		}
		// Remove leading and ending ampersand.
		v = v[1:]
		v = strings.TrimSuffix(v, "\"")

		stringSlice = append(stringSlice, v)
	}

	return &stringSlice, nil
}

// stringToNodes will Split the comma separated slice
// of nodes, and remove the start and end ampersand.
func stringToNode(s string) (*[]Node, error) {
	if s == "" {
		return nil, nil
	}

	var nodeSlice []Node

	sp := strings.Split(s, ",")

	for _, v := range sp {
		// Check if format is correct, return if not.
		pre := strings.HasPrefix(v, "\"")
		suf := strings.HasSuffix(v, "\"")
		if !pre || !suf {
			return nil, fmt.Errorf("stringToSlice: missing leading or ending ampersand")
		}
		// Remove leading and ending ampersand.
		v = v[1:]
		v = strings.TrimSuffix(v, "\"")

		nodeSlice = append(nodeSlice, Node(v))
	}

	return &nodeSlice, nil
}

// Will return the Label And the text Value of an input or dropdown form field.
func getLabelAndValue(fi tview.FormItem) (string, string) {
	var label string
	var value string
	switch v := fi.(type) {
	case *tview.InputField:
		value = v.GetText()
		label = v.GetLabel()
	case *tview.DropDown:
		label = v.GetLabel()
		_, value = v.GetCurrentOption()
	}

	return label, value
}

// Check if number is int.
func validateInteger(text string, ch rune) bool {
	if text == "-" {
		return true
	}
	_, err := strconv.Atoi(text)
	return err == nil
}

// getNodes will load all the node names from a file, and return a slice of
// string values, each representing a unique node.
func getNodeNames(fileName string) ([]string, error) {

	dirPath, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("error: tui: unable to get working directory: %v", err)
	}

	filePath := filepath.Join(dirPath, fileName)

	fh, err := os.Open(filePath)
	if err != nil {
		return nil, fmt.Errorf("error: tui: you should create a file named nodeslist.cfg with all your nodes : %v", err)
	}
	defer fh.Close()

	nodes := []string{}
	// append a blank node at the beginning of the slice, so the dropdown
	// can be set to blank
	nodes = append(nodes, "")

	scanner := bufio.NewScanner(fh)
	for scanner.Scan() {
		node := scanner.Text()
		nodes = append(nodes, node)
	}

	sort.Strings(nodes)

	return nodes, nil
}
