// Shared IO
package shio

import (
	"io"
	"sync"

	"novit.tech/direktil/initrd/colorio"
)

type ShIO struct {
	buf       []byte
	closed    bool
	cond      *sync.Cond
	hideInput bool
}

func New() *ShIO {
	return NewWithCap(1024)
}
func NewWithCap(capacity int) *ShIO {
	return NewWithBytes(make([]byte, 0, capacity))
}
func NewWithBytes(content []byte) *ShIO {
	return &ShIO{
		buf:  content,
		cond: sync.NewCond(&sync.Mutex{}),
	}
}

var (
	_ io.WriteCloser = New()
)

func (s *ShIO) Write(data []byte) (n int, err error) {
	s.cond.L.Lock()
	defer s.cond.L.Unlock()

	if s.closed {
		err = io.EOF
		return
	}

	if s.hideInput {
		s.buf = append(s.buf, colorio.Reset...)
	}

	s.buf = append(s.buf, data...)
	n = len(data)

	if s.hideInput {
		s.buf = append(s.buf, colorio.Hidden...)
	}

	s.cond.Broadcast()
	return
}

func (s *ShIO) HideInput() {
	s.cond.L.Lock()
	defer s.cond.L.Unlock()

	if s.closed {
		return
	}

	s.buf = append(s.buf, colorio.Hidden...)
	s.hideInput = true
}
func (s *ShIO) ShowInput() {
	s.cond.L.Lock()
	defer s.cond.L.Unlock()

	if s.closed {
		return
	}

	s.buf = append(s.buf, colorio.Reset...)
	s.hideInput = false
}

func (s *ShIO) Close() (err error) {
	s.cond.L.Lock()
	defer s.cond.L.Unlock()

	s.closed = true

	s.cond.Broadcast()
	return
}

func (s *ShIO) NewReader() io.Reader {
	return &shioReader{s, 0}
}

type shioReader struct {
	in  *ShIO
	pos int
}

func (r *shioReader) Read(ba []byte) (n int, err error) {
	r.in.cond.L.Lock()
	defer r.in.cond.L.Unlock()

	for r.pos == len(r.in.buf) && !r.in.closed {
		r.in.cond.Wait()
	}

	if r.pos == len(r.in.buf) && r.in.closed {
		err = io.EOF
		return
	}

	n = copy(ba, r.in.buf[r.pos:])
	r.pos += n

	return
}
