package progress

import (
	"io"
	"strings"
	"sync/atomic"

	"github.com/Tech-Gamer/nwy-wings/system"
)

// Progress is used to track the progress of any I/O operation that are being
// performed.
type Progress struct {
	// written is the total size of the files that have been written to the writer.
	written uint64
	// Total is the total size of the archive in bytes.
	total uint64

	// Writer .
	Writer io.Writer
}

// NewProgress returns a new progress tracker for the given total size.
func NewProgress(total uint64) *Progress {
	return &Progress{total: total}
}

// Written returns the total number of bytes written.
// This function should be used when the progress is tracking data being written.
func (p *Progress) Written() uint64 {
	return atomic.LoadUint64(&p.written)
}

// Total returns the total size in bytes.
func (p *Progress) Total() uint64 {
	return atomic.LoadUint64(&p.total)
}

// SetTotal sets the total size of the archive in bytes. This function is safe
// to call concurrently and can be used to update the total size if it changes,
// such as when the total size is simultaneously being calculated as data is
// being written through the progress writer.
func (p *Progress) SetTotal(total uint64) {
	atomic.StoreUint64(&p.total, total)
}

// Write totals the number of bytes that have been written to the writer.
func (p *Progress) Write(v []byte) (int, error) {
	n := len(v)
	atomic.AddUint64(&p.written, uint64(n))
	if p.Writer != nil {
		return p.Writer.Write(v)
	}
	return n, nil
}

// Progress returns a formatted progress string for the current progress.
func (p *Progress) Progress(width int) string {
	// current = 100 (Progress, dynamic)
	// total = 1000 (Content-Length, dynamic)
	// width = 25 (Number of ticks to display, static)
	// widthPercentage = 100 / width (What percentage does each tick represent, static)
	//
	// percentageDecimal = current / total = 0.1
	// percentage = percentageDecimal * 100 = 10%
	// ticks = percentage / widthPercentage = 2.5
	//
	// ticks is a float64, so we cast it to an int which rounds it down to 2.

	// Values are cast to floats to prevent integer division.
	current := p.Written()
	total := p.Total()
	// width := is passed as a parameter
	widthPercentage := float64(100) / float64(width)
	percentageDecimal := float64(current) / float64(total)
	percentage := percentageDecimal * 100
	ticks := int(percentage / widthPercentage)

	// Ensure that we never get a negative number of ticks, this will prevent strings#Repeat
	// from panicking.  A negative number of ticks is likely to happen when the total size is
	// inaccurate, such as when we are going off of rough disk usage calculation.
	if ticks < 0 {
		ticks = 0
	} else if ticks > width {
		ticks = width
	}

	bar := strings.Repeat("=", ticks) + strings.Repeat(" ", width-ticks)
	return "[" + bar + "] " + system.FormatBytes(current) + " / " + system.FormatBytes(total)
}
