package clitest

import (
	
	
	
	
	

	
	
	
)

const (
	// Maximum number of buffer updates FakeTTY expect to see.
	fakeTTYBufferUpdates = 4096
	// Maximum number of events FakeTTY produces.
	fakeTTYEvents = 4096
	// Maximum number of signals FakeTTY produces.
	fakeTTYSignals = 4096
)

// An implementation of the cli.TTY interface that is useful in tests.
type fakeTTY struct {
	setup func() (func(), error)
	// Channel that StartRead returns. Can be used to inject additional events.
	eventCh chan term.Event
	// Whether eventCh has been closed.
	eventChClosed bool
	// Mutex for synchronizing writing and closing eventCh.
	eventChMutex sync.Mutex
	// Channel for publishing updates of the main buffer and notes buffer.
	bufCh, notesBufCh chan *term.Buffer
	// Records history of the main buffer and notes buffer.
	bufs, notesBufs []*term.Buffer
	// Mutexes for guarding bufs and notesBufs.
	bufMutex sync.RWMutex
	// Channel that NotifySignals returns. Can be used to inject signals.
	sigCh chan os.Signal
	// Argument that SetRawInput got.
	raw int
	// Number of times the TTY screen has been cleared, incremented in
	// ClearScreen.
	cleared int

	sizeMutex sync.RWMutex
	// Predefined sizes.
	height, width int
}

// Initial size of fake TTY.
const (
	FakeTTYHeight = 20
	FakeTTYWidth  = 50
)

// NewFakeTTY creates a new FakeTTY and a handle for controlling it. The initial
// size of the terminal is FakeTTYHeight and FakeTTYWidth.
func () (cli.TTY, TTYCtrl) {
	 := &fakeTTY{
		eventCh:    make(chan term.Event, fakeTTYEvents),
		sigCh:      make(chan os.Signal, fakeTTYSignals),
		bufCh:      make(chan *term.Buffer, fakeTTYBufferUpdates),
		notesBufCh: make(chan *term.Buffer, fakeTTYBufferUpdates),
		height:     FakeTTYHeight, width: FakeTTYWidth,
	}
	return , TTYCtrl{}
}

// Delegates to the setup function specified using the SetSetup method of
// TTYCtrl, or return a nop function and a nil error.
func ( *fakeTTY) () (func(), error) {
	if .setup == nil {
		return func() {}, nil
	}
	return .setup()
}

// Returns the size specified by using the SetSize method of TTYCtrl.
func ( *fakeTTY) () (,  int) {
	.sizeMutex.RLock()
	defer .sizeMutex.RUnlock()
	return .height, .width
}

// Returns next event from t.eventCh.
func ( *fakeTTY) () (term.Event, error) {
	return <-.eventCh, nil
}

// Records the argument.
func ( *fakeTTY) ( int) {
	.raw = 
}

// Closes eventCh.
func ( *fakeTTY) () {
	.eventChMutex.Lock()
	defer .eventChMutex.Unlock()
	close(.eventCh)
	.eventChClosed = true
}

// Returns the last recorded buffer.
func ( *fakeTTY) () *term.Buffer {
	.bufMutex.RLock()
	defer .bufMutex.RUnlock()
	return .bufs[len(.bufs)-1]
}

// Records a nil buffer.
func ( *fakeTTY) () {
	.bufMutex.Lock()
	defer .bufMutex.Unlock()
	.recordBuf(nil)
}

// UpdateBuffer records a new pair of buffers, i.e. sending them to their
// respective channels and appending them to their respective slices.
func ( *fakeTTY) (,  *term.Buffer,  bool) error {
	.bufMutex.Lock()
	defer .bufMutex.Unlock()
	.recordNotesBuf()
	.recordBuf()
	return nil
}

func ( *fakeTTY) () {
}

func ( *fakeTTY) () {
}

func ( *fakeTTY) () {
	.cleared++
}

func ( *fakeTTY) () <-chan os.Signal { return .sigCh }

func ( *fakeTTY) () { close(.sigCh) }

func ( *fakeTTY) ( *term.Buffer) {
	.bufs = append(.bufs, )
	.bufCh <- 
}

func ( *fakeTTY) ( *term.Buffer) {
	.notesBufs = append(.notesBufs, )
	.notesBufCh <- 
}

// TTYCtrl is an interface for controlling a fake terminal.
type TTYCtrl struct{ *fakeTTY }

// GetTTYCtrl takes a TTY and returns a TTYCtrl and true, if the TTY is a fake
// terminal. Otherwise it returns an invalid TTYCtrl and false.
func ( cli.TTY) (TTYCtrl, bool) {
	,  := .(*fakeTTY)
	return TTYCtrl{}, 
}

// SetSetup sets the return values of the Setup method of the fake terminal.
func ( TTYCtrl) ( func(),  error) {
	.setup = func() (func(), error) {
		return , 
	}
}

// SetSize sets the size of the fake terminal.
func ( TTYCtrl) (,  int) {
	.sizeMutex.Lock()
	defer .sizeMutex.Unlock()
	.height, .width = , 
}

// Inject injects events to the fake terminal.
func ( TTYCtrl) ( ...term.Event) {
	for ,  := range  {
		.inject()
	}
}

func ( TTYCtrl) ( term.Event) {
	.eventChMutex.Lock()
	defer .eventChMutex.Unlock()
	if !.eventChClosed {
		.eventCh <- 
	}
}

// EventCh returns the underlying channel for delivering events.
func ( TTYCtrl) () chan term.Event {
	return .eventCh
}

// InjectSignal injects signals.
func ( TTYCtrl) ( ...os.Signal) {
	for ,  := range  {
		.sigCh <- 
	}
}

// RawInput returns the argument in the last call to the SetRawInput method of
// the TTY.
func ( TTYCtrl) () int {
	return .raw
}

// ScreenCleared returns the number of times ClearScreen has been called on the
// TTY.
func ( TTYCtrl) () int {
	return .cleared
}

// TestBuffer verifies that a buffer will appear within the timeout of 4
// seconds, and fails the test if it doesn't
func ( TTYCtrl) ( *testing.T,  *term.Buffer) {
	.Helper()
	 := testBuffer(, , .bufCh)
	if ! {
		.bufMutex.RLock()
		defer .bufMutex.RUnlock()
		 := .LastBuffer()
		.Logf("Last buffer: %s", .TTYString())
		if  == nil {
			 := .BufferHistory()
			for  := len() - 1;  >= 0; -- {
				if [] != nil {
					.Logf("Last non-nil buffer: %s", [].TTYString())
					return
				}
			}
		}
	}
}

// TestNotesBuffer verifies that a notes buffer will appear within the timeout of 4
// seconds, and fails the test if it doesn't
func ( TTYCtrl) ( *testing.T,  *term.Buffer) {
	.Helper()
	 := testBuffer(, , .notesBufCh)
	if ! {
		.bufMutex.RLock()
		defer .bufMutex.RUnlock()
		 := .NotesBufferHistory()
		.Logf("There has been %d notes buffers. None-nil ones are:", len())
		for ,  := range  {
			if  != nil {
				.Logf("#%d:\n%s", , .TTYString())
			}
		}
	}
}

// BufferHistory returns a slice of all buffers that have appeared.
func ( TTYCtrl) () []*term.Buffer {
	.bufMutex.RLock()
	defer .bufMutex.RUnlock()
	return .bufs
}

// LastBuffer returns the last buffer that has appeared.
func ( TTYCtrl) () *term.Buffer {
	.bufMutex.RLock()
	defer .bufMutex.RUnlock()
	if len(.bufs) == 0 {
		return nil
	}
	return .bufs[len(.bufs)-1]
}

// NotesBufferHistory returns a slice of all notes buffers that have appeared.
func ( TTYCtrl) () []*term.Buffer {
	.bufMutex.RLock()
	defer .bufMutex.RUnlock()
	return .notesBufs
}

func ( TTYCtrl) () *term.Buffer {
	.bufMutex.RLock()
	defer .bufMutex.RUnlock()
	if len(.notesBufs) == 0 {
		return nil
	}
	return .notesBufs[len(.notesBufs)-1]
}

// Tests that an expected buffer will appear within the timeout.
func ( *testing.T,  *term.Buffer,  <-chan *term.Buffer) bool {
	.Helper()

	 := time.After(testutil.ScaledMs(100))
	for {
		select {
		case  := <-:
			if reflect.DeepEqual(, ) {
				return true
			}
		case <-:
			.Errorf("Wanted buffer not shown")
			.Logf("Want: %s", .TTYString())
			return false
		}
	}
}