// Copyright © 2018 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only

package progress

import (
	"fmt"
	"time"
)

// Client is the interface a frontend must implement in order to be notified about
// the install progress
type Client interface {
	// Desc is called when a new progress unit is started
	Desc(desc string)

	// Partial is called on behalf of a MultiStep progress task - it's called for each
	// partial step completion
	Partial(total int, step int)

	// Step is called on behalf of a Loop progress task - it's called based on the
	// time returned by LoopWaitDuration
	Step()

	// Success is called whenever a progress task is completed successfully
	Success()

	// Failure is called whenever a progress task is failed to be completed
	Failure()

	// LoopWaitDuration gives the implementation the opportunity configure the loop progress
	// step period
	LoopWaitDuration() time.Duration
}

// Progress is the internal interface for the progress subsystem, currently we have
// two different implementations a MultiStep and a Loop based one
type Progress interface {
	Partial(step int)
	Success()
	Failure()
}

// BaseProgress is the common implementation between MultiStep and Loop progress
type BaseProgress struct {
	total int
}

// Loop defines the specific data for Loop progress implementation
type Loop struct {
	BaseProgress
	done chan bool
}

var (
	impl Client
)

// Set defines the default progress client implementation
func Set(pi Client) {
	impl = pi
}

// MultiStep creates a new MultiStep implementation
func MultiStep(total int, format string, a ...interface{}) Progress {
	if impl == nil {
		panic("No progress implementation was configured. Use progress.Set() before using progress.")
	}

	desc := fmt.Sprintf(format, a...)
	prg := &BaseProgress{total: total}
	impl.Desc(desc)
	return prg
}

func runStepLoop(prg *Loop, dur time.Duration) {
	for {
		select {
		case <-prg.done:
			return
		default:
			impl.Step()
			time.Sleep(dur)
		}
	}
}

// NewLoop creates a new Loop based progress implementation
func NewLoop(format string, a ...interface{}) Progress {
	if impl == nil {
		panic("No progress implementation was configured. Use progress.Set() before using progress.")
	}

	desc := fmt.Sprintf(format, a...)
	prg := &Loop{}
	prg.done = make(chan bool)

	impl.Desc(desc)
	go runStepLoop(prg, impl.LoopWaitDuration())

	return prg
}

// Success notifies the actual implementation we have finished a task
// successfully, this is the specific implementation for Loop based progress
func (prg *Loop) Success() {
	prg.done <- true
	impl.Success()
}

// Failure notifies the actual implementation we have finished a task
// unsuccessfully, this is the specific implementation for Loop based progress
func (prg *Loop) Failure() {
	prg.done <- true
	impl.Failure()
}

// Partial notifies the actual implementation we've moved one step on the
// set of steps for the MultiStep progress implementation
func (prg *BaseProgress) Partial(step int) {
	impl.Partial(prg.total, step)
}

// Success is the common BaseProgress implementation and simply notify the actual
// implementation we've finished a task successfully
func (prg *BaseProgress) Success() {
	impl.Success()
}

// Failure is the common BaseProgress implementation and simply notify the actual
// implementation we've finished a task unsuccessfully
func (prg *BaseProgress) Failure() {
	impl.Failure()
}
