package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/gdamore/tcell"
	"github.com/rivo/tview"
)

var (
	// Global channel to send the current directory to
	cwdch = make(chan string)

	// Global channel to send info about the current file, and
	// general status updates. Has to be buffered else the program
	// enters a deadlock for some reason
	statusch = make(chan string, 1)
)

func main() {
	var (
		// Main application
		app = tview.NewApplication()

		// Using a Grid layout allows us to put many primitives next to
		// each other and have them respond to the terminal size
		mainview = tview.NewGrid()

		// Show the files and directories inside the current, previous,
		// and next directory panels
		previousdir = tview.NewTable()
		currentdir  = tview.NewTable()
		nextdir     = tview.NewTable()

		// Small title showing the whole directory name
		cwd = tview.NewTextView()

		// Simple status bar showing current file permissions and other info
		fileinfo = tview.NewTextView()
	)

	// Init widgets
	initPanels(previousdir, currentdir, nextdir, app)
	initDirTitle(cwd, app)
	initFileInfo(fileinfo, app)

	// Set our mainview to resemble ranger
	mainview.
		SetBorders(true).
		SetColumns(10, 0, 40).
		SetRows(1, 0, 1)

	// Add the widgets
	mainview.
		AddItem(cwd, 0, 0, 1, 3, 1, 1, false).
		AddItem(previousdir, 1, 0, 1, 1, 1, 1, false).
		AddItem(currentdir, 1, 1, 1, 1, 1, 1, false).
		AddItem(nextdir, 1, 2, 1, 1, 1, 1, false).
		AddItem(fileinfo, 2, 0, 1, 3, 1, 1, true)

	// Make the mainview the root widget, and fullscreen it
	app.SetRoot(mainview, true)

	// Focus on the directory we are in
	app.SetFocus(currentdir)

	// Run the application
	err := app.Run()
	if err != nil {
		panic(err)
	}
}

// Preparation to properly initialize each panel upon start
func initPanels(previousdir, currentdir, nextdir *tview.Table, app *tview.Application) {
	// Make each selectable
	previousdir.SetSelectable(true, false)
	currentdir.SetSelectable(true, false)
	nextdir.SetSelectable(true, false)

	// Get the files and dirs of the current and previous directory
	getDirContents(previousdir, "..")
	getDirContents(currentdir, ".")

	// Define the function to be called each time we move to a new file or dir
	preview := func(row, column int) {
		currentselect := currentdir.GetCell(row, 0).Text
		previewDir(currentselect, nextdir)
	}

	// Use it on the first item
	preview(0, 0)

	// Install it
	currentdir.SetSelectionChangedFunc(preview)

	// Moving between dirs, and exiting
	currentdir.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Rune() {
		case 'h':
			// Move up
			err := os.Chdir("..")
			printError(err)

			copyDirToPanel(currentdir, nextdir)
			copyDirToPanel(previousdir, currentdir)
			getDirContents(previousdir, "..")

			// Update directory bar
			printCwd()

		case 'l':
			// Move into a dir
			r, _ := currentdir.GetSelection()
			currentItem := currentdir.GetCell(r, 0).Text

			// Get info about the current item
			fi, err := os.Stat(currentItem)
			printError(err)

			if fi.IsDir() {
				err = os.Chdir(currentItem)
				printError(err)

				copyDirToPanel(currentdir, previousdir)
				copyDirToPanel(nextdir, currentdir)
				preview(0, 0)
			}

			// Update directory bar
			printCwd()

		case 'q':
			app.Stop()
		}

		return event
	})
}

func initFileInfo(fi *tview.TextView, app *tview.Application) {
	// Display colors given by text
	fi.SetDynamicColors(true)

	// Constantly check and print file permissions etc
	go func() {
		var info string

		for {
			info = <-statusch

			fi.SetText(info)

			app.Draw()
		}
	}()
}

func initDirTitle(cwd *tview.TextView, app *tview.Application) {
	// Make the title blue and accept colors
	cwd.SetDynamicColors(true)
	cwd.SetTextColor(tcell.ColorBlue)

	// Get initial dir
	wd, err := os.Getwd()
	printError(err)

	// Check and print info about the current dir
	go func() {
		var dir string

		for {
			dir = <-cwdch

			cwd.SetText("[::b]" + filepath.Dir(dir) + "/[white]" + filepath.Base(dir))

			app.Draw()
		}
	}()

	cwdch <- wd
}

func copyDirToPanel(previousdir, currentdir *tview.Table) {
	// Make sure there are no elements in the panel
	currentdir.Clear()

	for i := 0; i < previousdir.GetRowCount(); i++ {
		currentdir.SetCell(i, 0, previousdir.GetCell(i, 0))
	}
}

func previewDir(path string, paneldir *tview.Table) {
	// Clean the panel
	paneldir.Clear()

	// Get info about the given path
	fi, err := os.Stat(path)
	if err != nil {
		return
	}

	if fi.IsDir() {
		getDirContents(paneldir, path)
	}

	// Send info about the current file
	statusch <- fmt.Sprintf("[fuchsia]%s[white]\t%s\t%s",
		fi.Mode().String(),
		fi.ModTime().Round(time.Second).String(),
		humanize.Bytes(uint64(fi.Size())))
}

func getDirContents(dirtable *tview.Table, path string) {
	// Make sure to the table is empty
	dirtable.Clear()

	// Get the contents of the directory
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return
	}

	for i, f := range files {
		var color = tcell.ColorWhite

		// Create a new cell for the table, and make sure it takes up
		// all the horizontal space
		cell := tview.NewTableCell(f.Name())
		cell.SetExpansion(1)

		// Colorize according to extension and mode
		if f.IsDir() {
			color = tcell.ColorNavy
			cell.SetAttributes(tcell.AttrBold)
		}

		switch filepath.Ext(f.Name()) {
		case ".png", ".jpg":
			color = tcell.ColorYellow

		case ".mp4", ".mkv", ".avi", ".webm", ".mp3", ".m4a", ".flac":
			color = tcell.ColorFuchsia

		case ".zip", ".gz", ".iso":
			color = tcell.ColorRed
		}

		if f.Mode()&os.ModeSymlink != 0 {
			color = tcell.ColorTeal
		}

		cell.SetTextColor(color)

		// Add it to the table on the i row, first column
		dirtable.SetCell(i, 0, cell)
	}
}

func printCwd() {
	dir, err := os.Getwd()
	printError(err)

	cwdch <- dir
}

func printError(err error) {
	if err != nil {
		statusch <- fmt.Sprintf("[red]%s", err)
	}
}
