package table

import (
	"fmt"
	"io"
	"sort"
	"strings"

	"github.com/miguelmota/cointop/cointop/common/table/align"
)

// Table table
type Table struct {
	cols             Cols
	rows             Rows
	sort             []SortBy
	width            int
	HideColumHeaders bool
}

// New new table
func New() *Table {
	return &Table{}
}

// SetWidth set table width
func (t *Table) SetWidth(w int) *Table {
	t.width = w
	return t
}

// AddCol add column
func (t *Table) AddCol(n string) *Col {
	c := &Col{name: n}
	t.cols = append(t.cols, c)
	return c
}

// AddRow add row
func (t *Table) AddRow(v ...interface{}) *Row {
	r := &Row{table: t, values: v, strValues: make([]string, len(v))}
	t.rows = append(t.rows, r)
	return r
}

// SortAscFn sort ascending function
func (t *Table) SortAscFn(n string, fn SortFn) *Table {
	i := t.cols.Index(n)
	s := SortBy{index: i, order: SortAsc, sortFn: fn}
	t.sort = append(t.sort, s)
	return t
}

// SortAsc sort ascending
func (t *Table) SortAsc(n string) *Table {
	return t.SortAscFn(n, nil)
}

// SortDescFn sort descending function
func (t *Table) SortDescFn(n string, fn SortFn) *Table {
	i := t.cols.Index(n)
	s := SortBy{index: i, order: SortDesc, sortFn: fn}
	t.sort = append(t.sort, s)
	return t
}

// SortDesc sort descending
func (t *Table) SortDesc(n string) *Table {
	return t.SortDescFn(n, nil)
}

// Sort sort
func (t *Table) Sort() *Table {
	if len(t.sort) > 0 {
		sort.Sort(t.rows)
	}
	return t
}

func (t *Table) colWidth() int {
	width := 0
	for _, c := range t.cols {
		if c.hide {
			continue
		}

		width += c.width
	}
	return width
}

func (t *Table) normalizeColWidthPerc() {
	perc := 0
	for _, c := range t.cols {
		if c.hide {
			continue
		}

		perc += c.minWidthPerc
	}

	for _, c := range t.cols {
		if c.hide {
			continue
		}

		c.perc = float32(c.minWidthPerc) / float32(perc)
	}
}

// Format format table
func (t *Table) Format() *Table {
	for _, c := range t.cols {
		c.width = len(c.name) + 1
		if c.minWidth > c.width {
			c.width = c.minWidth
		}
	}

	for _, r := range t.rows {
		for j, v := range r.values {
			c := t.cols[j]

			if c.hide {
				continue
			}

			if c.formatFn != nil {
				r.strValues[j] = fmt.Sprintf("%s", c.formatFn(v)) + " "
			} else if c.format != "" {
				r.strValues[j] = fmt.Sprintf(c.format, v) + " "
			} else {
				r.strValues[j] = fmt.Sprintf("%v", v) + " "
			}

			if len(r.strValues[j]) > t.cols[j].width {
				t.cols[j].width = len(r.strValues[j])
			}
		}
	}

	//t.normalizeColWidthPerc()
	unused := t.width - t.colWidth()
	if unused <= 0 {
		return t
	}

	for _, c := range t.cols {
		if c.hide {
			continue
		}

		if c.perc > 0 {
			c.width += int(float32(unused) * c.perc)
		}
	}

	var i int
	for i = len(t.cols) - 1; i >= 0; i-- {
		if t.cols[i].hide {
			continue
		}

		break
	}
	t.cols[i].width += t.width - t.colWidth()

	return t
}

// Fprint write
func (t *Table) Fprint(w io.Writer) {
	if !t.HideColumHeaders {
		for _, c := range t.cols {
			if c.hide {
				continue
			}

			var s string
			switch c.align {
			case AlignLeft:
				s = align.AlignLeft(c.name+" ", c.width)
			case AlignRight:
				s = align.AlignRight(c.name+" ", c.width)
			case AlignCenter:
				s = align.AlignCenter(c.name+" ", c.width)
			}

			fmt.Fprintf(w, "%s", s)
		}
		fmt.Fprintf(w, "\n")

		for _, c := range t.cols {
			if c.hide {
				continue
			}

			fmt.Fprintf(w, strings.Repeat("─", c.width))
		}
		fmt.Fprintf(w, "\n")
	}

	for _, r := range t.rows {
		for i, v := range r.strValues {
			c := t.cols[i]

			if c.hide {
				continue
			}

			var s string
			switch c.align {
			case AlignLeft:
				s = align.AlignLeft(v, c.width)
			case AlignRight:
				s = align.AlignRight(v, c.width)
			case AlignCenter:
				s = align.AlignCenter(v, c.width)
			}

			fmt.Fprintf(w, "%s", s)
		}
		fmt.Fprintf(w, "\n")
	}
}
