// Copyright 2012 Apcera Inc. All rights reserved.

package termtables

import (
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
	"unicode/utf8"

	runewidth "github.com/mattn/go-runewidth"
)

var (
	// Must match SGR escape sequence, which is "CSI Pm m", where the Control
	// Sequence Introducer (CSI) is "ESC ["; where Pm is "A multiple numeric
	// parameter composed of any number of single numeric parameters, separated
	// by ; character(s).  Individual values for the parameters are listed with
	// Ps" and where Ps is A single (usually optional) numeric parameter,
	// composed of one of [sic] more digits."
	//
	// In practice, the end sequence is usually given as \e[0m but reading that
	// definition, it's clear that the 0 is optional and some testing confirms
	// that it is certainly optional with MacOS Terminal 2.3, so we need to
	// support the string \e[m as a terminator too.
	colorFilter = regexp.MustCompile(`\033\[(?:\d+(?:;\d+)*)?m`)
)

// A Cell denotes one cell of a table; it spans one row and a variable number
// of columns.  A given Cell can only be used at one place in a table; the act
// of adding the Cell to the table mutates it with position information, so
// do not create one "const" Cell to add it multiple times.
type Cell struct {
	column         int
	formattedValue string
	alignment      *tableAlignment
	colSpan        int
}

// CreateCell returns a Cell where the content is the supplied value, with the
// optional supplied style (which may be given as nil).  The style can include
// a non-zero ColSpan to cause the cell to become column-spanning.  Changing
// the style afterwards will not adjust the column-spanning state of the cell
// itself.
func CreateCell(v interface{}, style *CellStyle) *Cell {
	return createCell(0, v, style)
}

func createCell(column int, v interface{}, style *CellStyle) *Cell {
	cell := &Cell{column: column, formattedValue: renderValue(v), colSpan: 1}
	if style != nil {
		cell.alignment = &style.Alignment
		if style.ColSpan != 0 {
			cell.colSpan = style.ColSpan
		}
	}
	return cell
}

// Width returns the width of the content of the cell, measured in runes as best
// as possible considering sophisticated Unicode.
func (c *Cell) Width() int {
	return runewidth.StringWidth(filterColorCodes(c.formattedValue))
}

// Filter out terminal bold/color sequences in a string.
// This supports only basic bold/color escape sequences.
func filterColorCodes(s string) string {
	return colorFilter.ReplaceAllString(s, "")
}

// Render returns a string representing the content of the cell, together with
// padding (to the widths specified) and handling any alignment.
func (c *Cell) Render(style *renderStyle) (buffer string) {
	// if no alignment is set, import the table's default
	if c.alignment == nil {
		c.alignment = &style.Alignment
	}

	// left padding
	buffer += strings.Repeat(" ", style.PaddingLeft)

	// append the main value and handle alignment
	buffer += c.alignCell(style)

	// right padding
	buffer += strings.Repeat(" ", style.PaddingRight)

	// this handles escaping for, eg, Markdown, where we don't care about the
	// alignment quite as much
	if style.replaceContent != nil {
		buffer = style.replaceContent(buffer)
	}

	return buffer
}

func (c *Cell) alignCell(style *renderStyle) string {
	buffer := ""
	width := style.CellWidth(c.column)

	if c.colSpan > 1 {
		for i := 1; i < c.colSpan; i++ {
			w := style.CellWidth(c.column + i)
			if w == 0 {
				break
			}
			width += style.PaddingLeft + w + style.PaddingRight + utf8.RuneCountInString(style.BorderY)
		}
	}

	switch *c.alignment {

	default:
		buffer += c.formattedValue
		if l := width - c.Width(); l > 0 {
			buffer += strings.Repeat(" ", l)
		}

	case AlignLeft:
		buffer += c.formattedValue
		if l := width - c.Width(); l > 0 {
			buffer += strings.Repeat(" ", l)
		}

	case AlignRight:
		if l := width - c.Width(); l > 0 {
			buffer += strings.Repeat(" ", l)
		}
		buffer += c.formattedValue

	case AlignCenter:
		left, right := 0, 0
		if l := width - c.Width(); l > 0 {
			lf := float64(l)
			left = int(math.Floor(lf / 2))
			right = int(math.Ceil(lf / 2))
		}
		buffer += strings.Repeat(" ", left)
		buffer += c.formattedValue
		buffer += strings.Repeat(" ", right)
	}

	return buffer
}

// Format the raw value as a string depending on the type
func renderValue(v interface{}) string {
	switch vv := v.(type) {
	case string:
		return vv
	case bool:
		return strconv.FormatBool(vv)
	case int:
		return strconv.Itoa(vv)
	case int64:
		return strconv.FormatInt(vv, 10)
	case uint64:
		return strconv.FormatUint(vv, 10)
	case float64:
		return strconv.FormatFloat(vv, 'f', 2, 64)
	case fmt.Stringer:
		return vv.String()
	}
	return fmt.Sprintf("%v", v)
}
