package readline

import (
	"fmt"
	"strconv"
	"strings"
)

// initList - List display details. Because of the way alternative completions
// are handled, MaxLength cannot be set when there are alternative completions.
func (g *CompletionGroup) initList(rl *Instance) {

	// We may only ever have two different
	// columns: (suggestions, and alternatives)
	g.tcMaxX = 2

	// We make the list anyway, especially if we need to use it later
	if g.Descriptions == nil {
		g.Descriptions = make(map[string]string)
	}
	if g.Aliases == nil {
		g.Aliases = make(map[string]string)
	}

	// Compute size of each completion item box. Group independent
	g.tcMaxLength = rl.getListPad()

	// Same for suggestions alt
	g.tcMaxLengthAlt = 0
	for i := range g.Suggestions {
		if len(g.Suggestions[i]) > g.tcMaxLength {
			g.tcMaxLength = len([]rune(g.Suggestions[i]))
		}
	}

	// Max values depend on if we have alternative suggestions
	if len(g.Aliases) == 0 {
		g.tcMaxX = 1
	} else {
		g.tcMaxX = 2
	}

	if len(g.Suggestions) > g.MaxLength {
		g.tcMaxY = g.MaxLength
	} else {
		g.tcMaxY = len(g.Suggestions)
	}

	g.tcPosX = 0
	g.tcPosY = 0
	g.tcOffset = 0
}

// moveTabListHighlight - Moves the highlighting for currently selected completion item (list display)
// We don't care about the x, because only can have 2 columns of selectable choices (--long and -s)
func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done bool, next bool) {

	// We dont' pass to x, because not managed by callers
	g.tcPosY += x
	g.tcPosY += y

	// Lines
	if g.tcPosY < 1 {
		if rl.tabCompletionReverse {
			if g.tcOffset > 0 {
				g.tcPosY = 1
				g.tcOffset--
			} else {
				return true, false
			}
		}
	}
	if g.tcPosY > g.tcMaxY {
		g.tcPosY--
		g.tcOffset++
	}

	// Once we get to the end of choices: check which column we were selecting.
	if g.tcOffset+g.tcPosY > len(g.Suggestions) {
		// If we have alternative options and that we are not yet
		// completing them, start on top of their column
		if g.tcPosX == 0 && len(g.Aliases) > 0 {
			g.tcPosX++
			g.tcPosY = 1
			g.tcOffset = 0
			return false, false
		}

		// Else no alternatives, return for next group.
		// Reset all values, in case we pass on them again.
		g.tcPosX = 0 // First column
		g.tcPosY = 1 // first row
		g.tcOffset = 0
		return true, true
	}

	// Here we must check, in x == 1, that the current choice
	// is not empty. Handle for both reverse and forward movements.
	sugg := g.Suggestions[g.tcPosY-1]
	_, ok := g.Aliases[sugg]
	if !ok && g.tcPosX == 1 {
		if rl.tabCompletionReverse {
			for i := len(g.Suggestions[:g.tcPosY-1]); i > 0; i-- {
				su := g.Suggestions[i]
				if _, ok := g.Aliases[su]; ok {
					g.tcPosY -= (len(g.Suggestions[:g.tcPosY-1])) - i
					return false, false
				}
			}
			g.tcPosX = 0
			g.tcPosY = g.tcMaxY

		} else {
			for i, su := range g.Suggestions[g.tcPosY-1:] {
				if _, ok := g.Aliases[su]; ok {
					g.tcPosY += i
					return false, false
				}
			}
		}
	}

	// Setup offset if needs to be.
	// TODO: should be rewrited to conditionally process rolling menus with alternatives
	if g.tcOffset+g.tcPosY < 1 && len(g.Suggestions) > 0 {
		g.tcPosY = g.tcMaxY
		g.tcOffset = len(g.Suggestions) - g.tcMaxY
	}
	if g.tcOffset < 0 {
		g.tcOffset = 0
	}

	// MIGHT BE NEEDED IF PROBLEMS WIHT ROLLING COMPLETIONS
	// ------------------------------------------------------------------------------
	// Once we get to the end of choices: check which column we were selecting.
	// We use +1 because we may have a single suggestion, and we just want "a ratio"
	// if g.tcOffset+g.tcPosY > len(g.Suggestions) {
	//
	//         // If we have alternative options and that we are not yet
	//         // completing them, start on top of their column
	//         if g.tcPosX == 1 && len(g.SuggestionsAlt) > 0 {
	//                 g.tcPosX++
	//                 g.tcPosY = 1
	//                 g.tcOffset = 0
	//                 return false
	//         }
	//
	//         // Else no alternatives, return for next group.
	//         g.tcPosY = 1
	//         return true
	// }
	return false, false
}

// writeList - A list completion string
func (g *CompletionGroup) writeList(rl *Instance) (comp string) {

	// Print group title and adjust offset if there is one.
	if g.Name != "" {
		comp += fmt.Sprintf("%s%s%s %s\n", BOLD, YELLOW, g.Name, RESET)
		rl.tcUsedY++
	}

	termWidth := GetTermWidth()
	if termWidth < 20 {
		// terminal too small. Probably better we do nothing instead of crash
		// We are more conservative than lmorg, and push it to 20 instead of 10
		return
	}

	// Suggestion cells dimensions
	maxLength := g.tcMaxLength
	if maxLength > termWidth-9 {
		maxLength = termWidth - 9
	}
	cellWidth := strconv.Itoa(maxLength)

	// Alternative suggestion cells dimensions
	maxLengthAlt := g.tcMaxLengthAlt + 2
	if maxLengthAlt > termWidth-9 {
		maxLengthAlt = termWidth - 9
	}
	cellWidthAlt := strconv.Itoa(maxLengthAlt)

	// Descriptions cells dimensions
	maxDescWidth := termWidth - maxLength - maxLengthAlt - 4

	// function highlights the cell depending on current selector place.
	highlight := func(y int, x int) string {
		if y == g.tcPosY && x == g.tcPosX && g.isCurrent {
			return seqInvert
		}
		return ""
	}

	// For each line in completions
	y := 0
	for i := g.tcOffset; i < len(g.Suggestions); i++ {
		y++ // Consider next item
		if y > g.tcMaxY {
			break
		}

		// Main suggestion
		item := g.Suggestions[i]
		if len(item) > maxLength {
			item = item[:maxLength-3] + "..."
		}
		sugg := fmt.Sprintf("\r%s%-"+cellWidth+"s", highlight(y, 0), fmtEscape(item))

		// Alt suggestion
		alt, ok := g.Aliases[item]
		if ok {
			alt = fmt.Sprintf(" %s%"+cellWidthAlt+"s", highlight(y, 1), fmtEscape(alt))
		} else {
			// Else, make an empty cell
			alt = strings.Repeat(" ", maxLengthAlt+1) // + 2 to keep account of spaces
		}

		// Description
		description := g.Descriptions[g.Suggestions[i]]
		if len(description) > maxDescWidth {
			description = description[:maxDescWidth-3] + "..." + RESET + "\n"
		} else {
			description += "\n"
		}

		// Total completion line
		comp += sugg + seqReset + alt + " " + seqReset + description
	}

	// Add the equivalent of this group's size to final screen clearing
	// Can be set and used only if no alterative completions have been given.
	if len(g.Aliases) == 0 {
		if len(g.Suggestions) > g.MaxLength {
			rl.tcUsedY += g.MaxLength
		} else {
			rl.tcUsedY += len(g.Suggestions)
		}
	} else {
		rl.tcUsedY += len(g.Suggestions)
	}

	return
}

func (rl *Instance) getListPad() (pad int) {
	for _, group := range rl.tcGroups {
		if group.DisplayType == TabDisplayList {
			for i := range group.Suggestions {
				if len(group.Suggestions[i]) > pad {
					pad = len([]rune(group.Suggestions[i]))
				}
			}
		}
	}

	return
}