package input

import (
	"context"
	"sync"
	"time"
)

type Buffered struct {
	ctx                context.Context
	can                context.CancelFunc
	lock               sync.Mutex
	keys               map[byte]int64
	input              Input
	listenInterval     time.Duration
	expirationInterval time.Duration
}

func NewBuffered(ctx context.Context, input Input) *Buffered {
	ctx, can := context.WithCancel(ctx)
	result := &Buffered{
		input:              input,
		ctx:                ctx,
		can:                can,
		lock:               sync.Mutex{},
		keys:               map[byte]int64{},
		listenInterval:     time.Millisecond * 20,
		expirationInterval: time.Millisecond * 100,
	}
	go result.listen()
	return result
}

func (b *Buffered) listen() {
	for b.ctx.Err() == nil {
		buttons := b.input.Read()

		b.lock.Lock()
		for i := range buttons {
			if buttons[i].Down {
				b.keys[buttons[i].Char] = time.Now().UnixNano()
			}
		}
		b.lock.Unlock()
		select {
		case <-b.ctx.Done():
		case <-time.After(b.listenInterval):
		}
	}
}

func (b *Buffered) Close() {
	b.input.Close()
	b.can()
}

func (b *Buffered) Read() []Button {
	for b.ctx.Err() == nil {
		result := b.read()
		if len(result) > 0 {
			return result
		}
		select {
		case <-b.ctx.Done():
		case <-time.After(b.listenInterval):
		}
	}
	return []Button{}
}

func (b *Buffered) read() []Button {
	b.lock.Lock()
	defer b.lock.Unlock()

	result := make([]Button, 0, len(b.keys))
	for k, v := range b.keys {
		isFresh := v > 0
		isStale := v < 0 && time.Since(time.Unix(0, -1*v)) > b.expirationInterval
		if isFresh || isStale {
			result = append(result, Button{Char: k, Down: isFresh})
		}
		if isFresh {
			b.keys[k] = -1 * v
		}
	}

	for i := range result {
		if !result[i].Down {
			delete(b.keys, result[i].Char)
		}
	}

	return result
}
