package template

import (
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/olekukonko/tablewriter"
)

type DiffPrinter struct {
	w      io.Writer
	writer *tablewriter.Table

	useColor bool
	title    string

	appendCalls int
	headerLen   int
}

func NewDiffPrinter(w io.Writer, hasColor, hasBorder bool) *DiffPrinter {
	wr := tablewriter.NewWriter(w)
	wr.SetBorder(hasBorder)
	wr.SetRowLine(hasBorder)

	return &DiffPrinter{
		w:        w,
		writer:   wr,
		useColor: hasColor,
	}
}

func (d *DiffPrinter) Render() {
	if d.appendCalls == 0 {
		return
	}

	// set the title and the add/remove legend
	title := strings.ToUpper(d.title)
	add := "+add"
	remove := "-remove"
	if d.useColor {
		title = colorTitle.Sprint(title)
		add = colorTotalAdd.Sprint(add)
		remove = colorTotalRemove.Sprint(remove)
	}
	fmt.Fprintf(d.w, "%s    %s | %s | unchanged\n", title, add, remove)

	d.setFooter()
	d.writer.Render()
}

func (d *DiffPrinter) Title(title string) *DiffPrinter {
	d.title = title
	return d
}

func (d *DiffPrinter) SetHeaders(headers ...string) *DiffPrinter {
	headers = d.prepend(headers, "+/-")
	d.headerLen = len(headers)

	d.writer.SetHeader(headers)

	headerColors := make([]tablewriter.Colors, d.headerLen)
	color := noColor
	if d.useColor {
		color = colorHeader
	}
	for i := range headerColors {
		headerColors[i] = color
	}
	d.writer.SetHeaderColor(headerColors...)

	return d
}

func (d *DiffPrinter) setFooter() *DiffPrinter {
	footers := make([]string, d.headerLen)
	if d.headerLen > 1 {
		footers[len(footers)-2] = "TOTAL"
		footers[len(footers)-1] = strconv.Itoa(d.appendCalls)
	} else {
		footers[0] = "TOTAL: " + strconv.Itoa(d.appendCalls)
	}

	d.writer.SetFooter(footers)
	colors := make([]tablewriter.Colors, d.headerLen)
	color := noColor
	if d.useColor {
		color = colorFooter
	}
	if d.headerLen > 1 {
		colors[len(colors)-2] = color
		colors[len(colors)-1] = color
	} else {
		colors[0] = color
	}
	d.writer.SetFooterColor(colors...)

	return d
}

func (d *DiffPrinter) Append(slc []string) {
	d.writer.Append(d.prepend(slc, ""))
}

// AppendDiff appends a diff to the diff printer
//
// assumeDiff says to mark remove/add as a diff (with two lines), even if they are the same.
// this is used for types that the CLI does not know how to fully compare.
func (d *DiffPrinter) AppendDiff(remove, add []string, assumeDiff bool) {
	defer func() { d.appendCalls++ }()

	if d.appendCalls > 0 {
		d.appendBufferLine()
	}

	lenAdd, lenRemove := len(add), len(remove)
	preppedAdd, preppedRemove := d.prepend(add, "+"), d.prepend(remove, "-")
	if lenRemove > 0 && lenAdd == 0 {
		d.writer.Rich(preppedRemove, d.redRow(len(preppedRemove)))
		return
	}
	if lenAdd > 0 && lenRemove == 0 {
		d.writer.Rich(preppedAdd, d.greenRow(len(preppedAdd)))
		return
	}

	var (
		addColors    = make([]tablewriter.Colors, len(preppedAdd))
		removeColors = make([]tablewriter.Colors, len(preppedRemove))
		hasDiff      = assumeDiff
	)
	addColor, removeColor := noColor, noColor
	if d.useColor {
		addColor, removeColor = colorAdd, colorRemove
	}
	for i := 0; i < lenRemove; i++ {
		if add[i] != remove[i] {
			hasDiff = true
			// offset to skip prepended +/- column
			addColors[i+1], removeColors[i+1] = addColor, removeColor
		}
	}

	if !hasDiff {
		d.writer.Append(d.prepend(add, ""))
		return
	}

	addColors[0], removeColors[0] = addColor, removeColor
	d.writer.Rich(d.prepend(remove, "-"), removeColors)
	d.writer.Rich(d.prepend(add, "+"), addColors)
}

func (d *DiffPrinter) appendBufferLine() {
	d.writer.Append([]string{})
}

func (d *DiffPrinter) redRow(i int) []tablewriter.Colors {
	return d.colorRow(colorRemove, i)
}

func (d *DiffPrinter) greenRow(i int) []tablewriter.Colors {
	return d.colorRow(colorAdd, i)
}

func (d *DiffPrinter) prepend(slc []string, val string) []string {
	return append([]string{val}, slc...)
}

func (d *DiffPrinter) colorRow(color tablewriter.Colors, i int) []tablewriter.Colors {
	colors := make([]tablewriter.Colors, i)
	for i := range colors {
		if d.useColor {
			colors[i] = color
		} else {
			colors[i] = noColor
		}
	}
	return colors
}
