package formatter

import (
	"fmt"
	"sync"

	"github.com/docker/go-units"
)

const (
	defaultStatsTableFormat    = "table {{.Container}}\t{{.CPUPrec}}\t{{.MemUsage}}\t{{.MemPrec}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
	winDefaultStatsTableFormat = "table {{.Container}}\t{{.CPUPrec}}\t{{{.MemUsage}}\t{.NetIO}}\t{{.BlockIO}}"
	emptyStatsTableFormat      = "Waiting for statistics..."

	containerHeader  = "CONTAINER"
	cpuPrecHeader    = "CPU %"
	netIOHeader      = "NET I/O"
	blockIOHeader    = "BLOCK I/O"
	winMemPrecHeader = "PRIV WORKING SET"  // Used only on Window
	memPrecHeader    = "MEM %"             // Used only on Linux
	memUseHeader     = "MEM USAGE / LIMIT" // Used only on Linux
	pidsHeader       = "PIDS"              // Used only on Linux
)

// ContainerStatsAttrs represents the statistics data collected from a container.
type ContainerStatsAttrs struct {
	Windows          bool
	Name             string
	CPUPercentage    float64
	Memory           float64 // On Windows this is the private working set
	MemoryLimit      float64 // Not used on Windows
	MemoryPercentage float64 // Not used on Windows
	NetworkRx        float64
	NetworkTx        float64
	BlockRead        float64
	BlockWrite       float64
	PidsCurrent      uint64 // Not used on Windows
}

// ContainerStats represents the containers statistics data.
type ContainerStats struct {
	Mu sync.RWMutex
	ContainerStatsAttrs
	Err error
}

// NewStatsFormat returns a format for rendering an CStatsContext
func NewStatsFormat(source, osType string) Format {
	if source == TableFormatKey {
		if osType == "windows" {
			return Format(winDefaultStatsTableFormat)
		}
		return Format(defaultStatsTableFormat)
	}
	return Format(source)
}

// NewContainerStats returns a new ContainerStats entity and sets in it the given name
func NewContainerStats(name, osType string) *ContainerStats {
	return &ContainerStats{
		ContainerStatsAttrs: ContainerStatsAttrs{
			Name:    name,
			Windows: (osType == "windows"),
		},
	}
}

// ContainerStatsWrite renders the context for a list of containers statistics
func ContainerStatsWrite(ctx Context, containerStats []*ContainerStats) error {
	render := func(format func(subContext subContext) error) error {
		for _, cstats := range containerStats {
			cstats.Mu.RLock()
			cstatsAttrs := cstats.ContainerStatsAttrs
			cstats.Mu.RUnlock()
			containerStatsCtx := &containerStatsContext{
				s: cstatsAttrs,
			}
			if err := format(containerStatsCtx); err != nil {
				return err
			}
		}
		return nil
	}
	return ctx.Write(&containerStatsContext{}, render)
}

type containerStatsContext struct {
	HeaderContext
	s ContainerStatsAttrs
}

func (c *containerStatsContext) Container() string {
	c.AddHeader(containerHeader)
	return c.s.Name
}

func (c *containerStatsContext) CPUPrec() string {
	c.AddHeader(cpuPrecHeader)
	return fmt.Sprintf("%.2f%%", c.s.CPUPercentage)
}

func (c *containerStatsContext) MemUsage() string {
	c.AddHeader(memUseHeader)
	if !c.s.Windows {
		return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
	}
	return fmt.Sprintf("-- / --")
}

func (c *containerStatsContext) MemPrec() string {
	header := memPrecHeader
	if c.s.Windows {
		header = winMemPrecHeader
	}
	c.AddHeader(header)
	return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
}

func (c *containerStatsContext) NetIO() string {
	c.AddHeader(netIOHeader)
	return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3))
}

func (c *containerStatsContext) BlockIO() string {
	c.AddHeader(blockIOHeader)
	return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3))
}

func (c *containerStatsContext) PIDs() string {
	c.AddHeader(pidsHeader)
	if !c.s.Windows {
		return fmt.Sprintf("%d", c.s.PidsCurrent)
	}
	return fmt.Sprintf("-")
}