// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package portlist

import (
	"context"
	"errors"
	"runtime"
	"time"

	"tailscale.com/version"
)

// Poller scans the systems for listening ports periodically and sends
// the results to C.
type Poller struct {
	// C received the list of ports periodically. It's closed when
	// Run completes, after which Err can be checked.
	C <-chan List

	c chan List

	// Err is the error from the final GetList call. It is only
	// valid to read once C has been closed. Err is nil if Close
	// is called or the context is canceled.
	Err error

	quitCh chan struct{} // close this to force exit
	prev   List          // most recent data
}

// NewPoller returns a new portlist Poller. It returns an error
// if the portlist couldn't be obtained.
func NewPoller() (*Poller, error) {
	if runtime.GOOS == "darwin" && version.IsMobile() {
		return nil, errors.New("not available on iOS")
	}
	p := &Poller{
		c:      make(chan List),
		quitCh: make(chan struct{}),
	}
	p.C = p.c

	// Do one initial poll synchronously so we can return an error
	// early.
	var err error
	p.prev, err = GetList(nil)
	if err != nil {
		return nil, err
	}
	return p, nil
}

func (p *Poller) Close() error {
	select {
	case <-p.quitCh:
		return nil
	default:
	}
	close(p.quitCh)
	<-p.C
	return nil
}

// Run runs the Poller periodically until either the context
// is done, or the Close is called.
func (p *Poller) Run(ctx context.Context) error {
	defer close(p.c)
	tick := time.NewTicker(pollInterval)
	defer tick.Stop()

	// Send out the pre-generated initial value
	p.c <- p.prev

	for {
		select {
		case <-tick.C:
			pl, err := GetList(p.prev)
			if err != nil {
				p.Err = err
				return err
			}
			if pl.SameInodes(p.prev) {
				continue
			}
			p.prev = pl
			select {
			case p.c <- pl:
			case <-ctx.Done():
				return ctx.Err()
			case <-p.quitCh:
				return nil
			}
		case <-ctx.Done():
			return ctx.Err()
		case <-p.quitCh:
			return nil
		}
	}
}