package term

import (
	"errors"
	"sync"
)

type TermRef struct {
	term   *Term
	refcnt int
}

func NewTermRef(term *Term) *TermRef {
	return &TermRef{
		term:   term,
		refcnt: 1,
	}
}

type TermStore struct {
	sync.Mutex
	terms map[string]*TermRef
}

var termStore = &TermStore{
	terms: make(map[string]*TermRef),
}

func (store *TermStore) All() []*Term {
	store.Lock()
	defer store.Unlock()

	terms := make([]*Term, 0, len(store.terms))
	for _, t := range store.terms {
		terms = append(terms, t.term)
	}

	return terms
}

func (store *TermStore) New(o TermOption) (*Term, error) {
	if o.Port == 0 {
		o.Port = 22
	}

	l := &TermLink{Host: o.Host, Port: o.Port}

	err := l.Dial(o.Username, o.Password)
	if err != nil {
		return nil, err
	}

	if o.Rows == 0 || o.Cols == 0 {
		o.Rows = 40
		o.Cols = 80
	}

	term, err := l.NewTerm(o.Rows, o.Cols)
	if err != nil {
		l.Close()
		return nil, err
	}

	// create new term ref object and add to store
	store.Lock()
	defer store.Unlock()

	ref := NewTermRef(term)
	store.terms[term.Id] = ref

	return term, nil
}

// Lookup do not increment refcnt!
func (store *TermStore) Lookup(id string) (*Term, error) {
	store.Lock()
	defer store.Unlock()

	ref, okay := store.terms[id]
	if !okay {
		return nil, errors.New("term " + id + " not exist")
	}

	return ref.term, nil
}

func (store *TermStore) Get(id string) (*Term, error) {
	store.Lock()
	defer store.Unlock()

	ref, okay := store.terms[id]
	if !okay {
		return nil, errors.New("term " + id + " not exist")
	}

	ref.refcnt++
	defer func() {
		if r := recover(); r != nil {
			ref.refcnt--
		}
	}()

	return ref.term, nil
}

func (store *TermStore) Put(id string) {
	store.Lock()
	defer store.Unlock()

	ref, okay := store.terms[id]
	if !okay {
		return
	}

	ref.refcnt--
	if ref.refcnt == 0 {
		delete(store.terms, id)
		ref.term.Close()
	}
}

func (store *TermStore) Do(id string, f func(term *Term) error) error {
	store.Lock()
	defer store.Unlock()

	ref, ok := store.terms[id]
	if !ok {
		return errors.New("term " + id + " not exist")
	}

	return f(ref.term)
}
