package mpb

import (
	"io"
	"unicode/utf8"

	"github.com/vbauerster/mpb/v4/decor"
	"github.com/vbauerster/mpb/v4/internal"
)

const (
	rLeft = iota
	rFill
	rTip
	rEmpty
	rRight
	rRevTip
	rRefill
)

// DefaultBarStyle is applied when bar constructed with *Progress.AddBar method.
//
//	'1th rune' stands for left boundary rune
//
//	'2th rune' stands for fill rune
//
//	'3th rune' stands for tip rune
//
//	'4th rune' stands for empty rune
//
//	'5th rune' stands for right boundary rune
//
//	'6th rune' stands for reverse tip rune
//
//	'7th rune' stands for refill rune
//
const DefaultBarStyle string = "[=>-]<+"

type barFiller struct {
	format  [][]byte
	tip     []byte
	refill  int64
	reverse bool
	flush   func(w io.Writer, bb [][]byte)
}

// NewBarFiller constucts mpb.Filler, to be used with *Progress.Add method.
func NewBarFiller(style string, reverse bool) Filler {
	if style == "" {
		style = DefaultBarStyle
	}
	bf := &barFiller{
		format: make([][]byte, utf8.RuneCountInString(style)),
	}
	bf.SetStyle(style)
	bf.SetReverse(reverse)
	return bf
}

func (s *barFiller) SetStyle(style string) {
	if !utf8.ValidString(style) {
		return
	}
	src := make([][]byte, 0, utf8.RuneCountInString(style))
	for _, r := range style {
		src = append(src, []byte(string(r)))
	}
	copy(s.format, src)
	if s.reverse {
		s.tip = s.format[rRevTip]
	} else {
		s.tip = s.format[rTip]
	}
}

func (s *barFiller) SetReverse(reverse bool) {
	if reverse {
		s.tip = s.format[rRevTip]
		s.flush = func(w io.Writer, bb [][]byte) {
			for i := len(bb) - 1; i >= 0; i-- {
				w.Write(bb[i])
			}
		}
	} else {
		s.tip = s.format[rTip]
		s.flush = func(w io.Writer, bb [][]byte) {
			for i := 0; i < len(bb); i++ {
				w.Write(bb[i])
			}
		}
	}
	s.reverse = reverse
}

func (s *barFiller) SetRefill(amount int64) {
	s.refill = amount
}

func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) {
	// don't count rLeft and rRight as progress
	width -= 2
	if width < 2 {
		return
	}
	w.Write(s.format[rLeft])
	defer w.Write(s.format[rRight])

	bb := make([][]byte, width)

	cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width))

	for i := 0; i < cwidth; i++ {
		bb[i] = s.format[rFill]
	}

	if s.refill > 0 {
		var rwidth int
		if s.refill > stat.Current {
			rwidth = cwidth
		} else {
			rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width))
		}
		for i := 0; i < rwidth; i++ {
			bb[i] = s.format[rRefill]
		}
	}

	if cwidth > 0 && cwidth < width {
		bb[cwidth-1] = s.tip
	}

	for i := cwidth; i < width; i++ {
		bb[i] = s.format[rEmpty]
	}

	s.flush(w, bb)
}
