package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net"
	"os/exec"
	"strconv"
	"strings"

	"github.com/gdamore/tcell"
	"github.com/guillermo/go.procmeminfo"
	"github.com/rivo/tview"
)

const (
	CMD_SYSTEMCTL = "/usr/bin/systemctl"
	CMD_FINDMNT   = "/usr/bin/findmnt"
	CMD_VCGENCMD  = "/opt/vc/bin/vcgencmd"
	FILE_CPU_TEMP = "/sys/class/thermal/thermal_zone0/temp"
)

const (
	PAGE_MAIN_UI  = "PAGE_MAIN_UI"
	PAGE_EXIT     = "PAGE_EXIT"
	PAGE_REBOOT   = "PAGE_REBOOT"
	PAGE_POWEROFF = "PAGE_POWEROFF"
)

func main() {
	// Memory info for status panel
	meminfo := &procmeminfo.MemInfo{}
	err := meminfo.Update()
	if err != nil {
		log.Printf("Error getting memory info : %s", err)
	}

	// Network interfaces for status panel
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Printf("Error getting netork interfaces : %s", err)
		return
	}

	// Disk use
	findmntOut, _ := exec.Command(CMD_FINDMNT, "-n", "-l",
		"-o", "TARGET,USE%",
		"-t", "ext4,exfat,vfat,btrfs,zfs,xfs").Output()
	filesystems := strings.Split(strings.Trim(string(findmntOut), "\n"), "\n")

	// GPU Temp
	vcgencmdOut, _ := exec.Command(CMD_VCGENCMD, "measure_temp").Output()
	gpuTemp := strings.Split(strings.Trim(string(vcgencmdOut), "\n"), "=")[1]

	// CPU Temp
	cpuTempFileContents, err := ioutil.ReadFile(FILE_CPU_TEMP)
	if err != nil {
		log.Fatalf("Error reading file %s : %s", FILE_CPU_TEMP, err)
	}
	cpuTempStr := strings.Trim(string(cpuTempFileContents), "\n")
	cpuTempInt, _ := strconv.Atoi(cpuTempStr)
	cpuTemp := fmt.Sprintf("%.2f'C", float64(cpuTempInt)/1000.0)

	// Run config UI when slideshow stops
	app := tview.NewApplication()

	// Header
	headerTitle := tview.NewTextView().
		SetText("PiFrame").
		SetTextAlign(tview.AlignCenter).
		SetTextColor(tcell.ColorAqua)

	headerSubTitle := tview.NewTextView().
		SetText("Management Utility").
		SetTextAlign(tview.AlignCenter).
		SetTextColor(tcell.ColorSilver)
	header := tview.NewFlex().
		SetDirection(tview.FlexRow)
	header.AddItem(headerTitle, 0, 1, false).
		AddItem(headerSubTitle, 0, 1, false)

	// Footer fields (Left Column)
	exitButton := tview.NewButton("Exit").
		SetBackgroundColorActivated(tcell.ColorGray)
	exitButton.SetLabelColor(tcell.ColorBlack).
		SetBorder(true).
		SetBorderColor(tcell.ColorBlack).
		SetBackgroundColor(tcell.ColorGreen).
		SetRect(0, 0, 22, 3)

	rebootButton := tview.NewButton("Reboot").
		SetBackgroundColorActivated(tcell.ColorGray)
	rebootButton.SetLabelColor(tcell.ColorBlack).
		SetBorder(true).
		SetBorderColor(tcell.ColorBlack).
		SetBackgroundColor(tcell.ColorYellow).
		SetRect(0, 0, 22, 3)

	powerOffButton := tview.NewButton("Power Off").
		SetBackgroundColorActivated(tcell.ColorGray)
	powerOffButton.SetLabelColor(tcell.ColorBlack).
		SetBorder(true).
		SetBorderColor(tcell.ColorBlack).
		SetBackgroundColor(tcell.ColorRed).
		SetRect(0, 0, 22, 3)

	// Footer
	footer := tview.NewFlex()
	footer.AddItem(exitButton, 0, 1, false).
		AddItem(rebootButton, 0, 1, false).
		AddItem(powerOffButton, 0, 1, false)
	// Setup menu
	menu := tview.NewList()
	menu.SetBorder(true).
		SetTitle("Menu").
		SetTitleColor(tcell.ColorAqua)
	menu.AddItem("Select Albums", "", '1', nil)
	menu.AddItem("Configure WiFi", "", '2', nil)

	// Setup base var for main column so the menu setup is easier to manage
	main := tview.NewFlex().
		SetDirection(tview.FlexRow)

	// Setup main panel (Center column)
	main.SetTitle("").
		SetBorder(true).
		SetTitleColor(tcell.ColorAqua)

	// WiFi Config Form
	wifiConfigForm := tview.NewForm()
	wifiConfigForm.AddInputField("Access Point", "", 0, nil, nil)
	wifiConfigForm.AddPasswordField("Password", "", 0, '*', nil)
	wifiConfigForm.AddButton("Apply", nil)
	wifiConfigForm.AddButton("Cancel", func() {
		main.Clear()
		app.SetFocus(menu)
	})

	// Select Albums Form
	selectAlbumsForm := tview.NewForm()
	selectAlbumsForm.AddCheckbox("Album 1", false, nil)
	selectAlbumsForm.AddCheckbox("Album 2", false, nil)
	selectAlbumsForm.AddButton("Apply", nil)
	selectAlbumsForm.AddButton("Cancel", func() {
		main.Clear()
		app.SetFocus(menu)
	})

	// Setup menu selection handler
	menu.SetSelectedFunc(func(index int, title string, desc string, shortcut rune) {
		if title == "Select Albums" {
			main.SetTitle("Select Albums")
			main.Clear()
			main.AddItem(selectAlbumsForm, 0, 1, true)
			app.SetFocus(selectAlbumsForm)
		}
		if title == "Configure WiFi" {
			main.SetTitle("Configure WiFi")
			main.Clear()
			main.AddItem(wifiConfigForm, 0, 1, true)
			app.SetFocus(wifiConfigForm)
		}
	})

	// Side bar fields
	sideBarCPUTempTitle := tview.NewTextView().
		SetText("CPU Temperature").
		SetTextColor(tcell.ColorYellow)
	sideBarCPUTemp := tview.NewTextView().
		SetText(fmt.Sprintf("  %s", cpuTemp))
	sideBarGPUTempTitle := tview.NewTextView().
		SetText("GPU Temperature").
		SetTextColor(tcell.ColorYellow)
	sideBarGPUTemp := tview.NewTextView().
		SetText(fmt.Sprintf("  %s", gpuTemp))
	sideBarMemoryTitle := tview.NewTextView().
		SetText("Memory Use (Mb)").
		SetTextColor(tcell.ColorYellow)
	divisor := math.Pow(1024.0, 2.0)
	sideBarMemoryStats := tview.NewTextView().
		SetText(fmt.Sprintf("  %.1f / %.1f",
			float64(meminfo.Used())/divisor,
			float64(meminfo.Total())/divisor))
	sideBarSwapTitle := tview.NewTextView().
		SetText("Swap Use").
		SetTextColor(tcell.ColorYellow)
	sideBarSwapStats := tview.NewTextView().
		SetText(fmt.Sprintf("  %d%%", meminfo.Swap()))
	sideBarFilesystemTitle := tview.NewTextView().
		SetText("Disk Use").
		SetTextColor(tcell.ColorYellow)
	var sideBarFilesystems []*tview.TextView
	for _, i := range filesystems {
		filesystemAsTextView := tview.NewTextView().
			SetText(fmt.Sprintf("  %s", i))
		sideBarFilesystems = append(sideBarFilesystems, filesystemAsTextView)
	}
	sideBarIPAddressesTitle := tview.NewTextView().
		SetText("IP Addresses").
		SetTextColor(tcell.ColorYellow)
	var sideBarIPAddresses []*tview.TextView
	for _, i := range ifaces {
		addrs, err := i.Addrs()
		if err != nil {
			log.Printf("Error getting interface addresses : %s", err)
			continue
		}
		for _, a := range addrs {
			ipAsTextView := tview.NewTextView().
				SetText(fmt.Sprintf("  %v : %s", i.Name, a.String()))
			sideBarIPAddresses = append(sideBarIPAddresses, ipAsTextView)
		}
	}

	// Setup side bar (Right column)
	sideBar := tview.NewFlex().
		SetDirection(tview.FlexRow)
	sideBar.SetTitle("System Info").
		SetBorder(true).
		SetTitleColor(tcell.ColorAqua)
	sideBar.AddItem(sideBarCPUTempTitle, 1, 1, false)
	sideBar.AddItem(sideBarCPUTemp, 1, 1, false)
	sideBar.AddItem(sideBarGPUTempTitle, 1, 1, false)
	sideBar.AddItem(sideBarGPUTemp, 1, 1, false)
	sideBar.AddItem(sideBarMemoryTitle, 1, 1, false)
	sideBar.AddItem(sideBarMemoryStats, 1, 1, false)
	sideBar.AddItem(sideBarSwapTitle, 1, 1, false)
	sideBar.AddItem(sideBarSwapStats, 1, 1, false)
	sideBar.AddItem(sideBarFilesystemTitle, 1, 1, false)
	for _, filesystemAsTextView := range sideBarFilesystems {
		sideBar.AddItem(filesystemAsTextView, 1, 1, false)
	}
	sideBar.AddItem(sideBarIPAddressesTitle, 1, 1, false)
	for _, ipAsTextView := range sideBarIPAddresses {
		sideBar.AddItem(ipAsTextView, 1, 1, false)
	}

	// Pages
	pages := tview.NewPages()

	// Main UI
	mainUI := tview.NewGrid().
		SetRows(2, 0, 4).
		SetColumns(25, 50, 50).
		SetBorders(true).
		AddItem(header, 0, 0, 1, 3, 0, 0, false).
		AddItem(footer, 2, 0, 1, 3, 0, 0, false)

	mainUI.AddItem(menu, 1, 0, 1, 1, 0, 100, true).
		AddItem(main, 1, 1, 1, 1, 0, 100, false).
		AddItem(sideBar, 1, 2, 1, 1, 0, 100, false)

	pages.AddPage(PAGE_MAIN_UI, mainUI, true, true)

	// Button modals
	exitModal := tview.NewModal().
		SetText("Are you sure you want to [red]EXIT?").
		AddButtons([]string{"Yes", "Cancel"}).
		SetDoneFunc(func(buttonIndex int, buttonLabel string) {
			if buttonLabel == "Yes" {
				app.Stop()
			}
			pages.SwitchToPage(PAGE_MAIN_UI)
		})
	pages.AddPage(PAGE_EXIT, exitModal, true, false)
	exitButton.SetSelectedFunc(func() {
		pages.ShowPage(PAGE_EXIT)
	})

	rebootModal := tview.NewModal().
		SetText("Are you sure you want to [red]REBOOT?").
		AddButtons([]string{"Yes", "Cancel"}).
		SetDoneFunc(func(buttonIndex int, buttonLabel string) {
			if buttonLabel == "Yes" {
				err := exec.Command(CMD_SYSTEMCTL, "reboot").Run()
				if err != nil {
					log.Fatalf("Could not reboot : %s ", err)
				}
			}
			pages.SwitchToPage(PAGE_MAIN_UI)
		})
	pages.AddPage(PAGE_REBOOT, rebootModal, true, false)
	rebootButton.SetSelectedFunc(func() {
		pages.ShowPage(PAGE_REBOOT)
	})

	powerOffModal := tview.NewModal().
		SetText("Are you sure you want to [red]POWER [red]OFF?").
		AddButtons([]string{"Yes", "Cancel"}).
		SetDoneFunc(func(buttonIndex int, buttonLabel string) {
			if buttonLabel == "Yes" {
				err := exec.Command(CMD_SYSTEMCTL, "poweroff").Run()
				if err != nil {
					log.Fatalf("Could not power off : %s ", err)
				}
			}
			pages.SwitchToPage(PAGE_MAIN_UI)
		})
	pages.AddPage(PAGE_POWEROFF, powerOffModal, true, false)
	powerOffButton.SetSelectedFunc(func() {
		pages.ShowPage(PAGE_POWEROFF)
	})

	// Setup tracking of which are of the UI can/has focus
	primitivesThatCanFocus := []tview.Primitive{menu, exitButton, rebootButton, powerOffButton}
	currentFocus := 0

	// Setup basic switching between main menu and buttons for the UI
	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		// Don't process input if we aren't on an existing element that can focus
		canContinue := false
		focusedPrimitive := app.GetFocus()

		// Override some of the default behavior so up/dn move between fields in forms
		//     Per API GetFocusedItemIndex on a form will be -1 if a form item isn't currently focused
		//     We use this as a bit of a cheat to figure out if we're inside of a form that needs better nav options for users (ie. tab doesn't exist on a remote)
		wifiField, wifiButton := wifiConfigForm.GetFocusedItemIndex()
		albumField, albumButton := selectAlbumsForm.GetFocusedItemIndex()
		if  wifiField != -1 || wifiButton != -1 || albumField != -1 || albumButton != -1  {
			switch event.Key() {
			case tcell.KeyUp:
				return tcell.NewEventKey(tcell.KeyBacktab, 0, event.Modifiers())
			case tcell.KeyDown:
				return tcell.NewEventKey(tcell.KeyTab, 0, event.Modifiers())
			}
		}

		// Standard override for the screen primitives we are using (ie. don't take over control in modals)
		for _, primitive := range primitivesThatCanFocus {
			if primitive == focusedPrimitive {
				canContinue = true
				break
			}
		}

		// Bail if we shouldn't be affecting user input
		if !canContinue {
			return event
		}

		// Add various forms of nav that make sense for a TUI (common stuff folks are used to)
		currentFocusChanged := false
		switch event.Key() {
		case tcell.KeyRight, tcell.KeyTab:
			currentFocus += 1
			if currentFocus >= len(primitivesThatCanFocus) {
				currentFocus = 0
			}
			currentFocusChanged = true
		case tcell.KeyLeft, tcell.KeyBacktab:
			currentFocus -= 1
			if currentFocus < 0 {
				currentFocus = len(primitivesThatCanFocus) - 1
			}
			currentFocusChanged = true
		}

		// Update the focus based on navigation
		if currentFocusChanged {
			app.SetFocus(primitivesThatCanFocus[currentFocus])
			return nil
		}

		// Pass through event so the main UI can process stuff properly
		return event
	})

	// Show UI and panic if there are any errors
	if err := app.SetRoot(pages, true).SetFocus(primitivesThatCanFocus[currentFocus]).EnableMouse(false).Run(); err != nil {
		log.Fatalf("Failed to run UI : ", err)
	}
}
