// Copyright 2017 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// Package gpiostream defines digital streams.
//
// Warning
//
// This package is still in flux as development is on-going.
package gpiostream

import (
	"fmt"
	"time"

	"periph.io/x/conn/v3/gpio"
	"periph.io/x/conn/v3/physic"
	"periph.io/x/conn/v3/pin"
)

// Stream is the interface to define a generic stream.
type Stream interface {
	// Frequency is the minimum data rate at which the binary stream is usable.
	//
	// For example, a bit stream may have a 10kHz data rate.
	Frequency() physic.Frequency
	// Duration of the binary stream. For infinitely looping streams, it is the
	// duration of the non-looping part.
	Duration() time.Duration
}

// BitStream is a stream of bits to be written or read.
type BitStream struct {
	// Bits is a densely packed bitstream.
	//
	// The stream is required to be a multiple of 8 samples.
	Bits []byte
	// Freq is the rate at each the bit (not byte) stream should be processed.
	Freq physic.Frequency
	// LSBF when true means than Bits is in LSB-first. When false, the data is
	// MSB-first.
	//
	// With MSBF, the first bit processed is the most significant one (0x80). For
	// example, I²C, I2S PCM and SPI use MSB-first at the word level. This
	// requires to pack words correctly.
	//
	// With LSBF, the first bit processed is the least significant one (0x01).
	// For example, Ethernet uses LSB-first at the byte level and MSB-first at
	// the word level.
	LSBF bool
}

// Frequency implements Stream.
func (b *BitStream) Frequency() physic.Frequency {
	return b.Freq
}

// Duration implements Stream.
func (b *BitStream) Duration() time.Duration {
	if b.Freq == 0 {
		return 0
	}
	return b.Freq.Period() * time.Duration(len(b.Bits)*8)
}

// GoString implements fmt.GoStringer.
func (b *BitStream) GoString() string {
	return fmt.Sprintf("&gpiostream.BitStream{Bits: %x, Freq:%s, LSBF:%t}", b.Bits, b.Freq, b.LSBF)
}

// EdgeStream is a stream of edges to be written.
//
// This struct is more efficient than BitStream for short repetitive pulses,
// like controlling a servo. A PWM can be created by specifying a slice of
// twice the same resolution and make it looping via a Program.
type EdgeStream struct {
	// Edges is the list of Level change. It is assumed that the signal starts
	// with gpio.High. Use a duration of 0 for Edges[0] to start with a Low
	// instead of the default High.
	//
	// The value is a multiple of Res. Use a 0 value to 'extend' a continuous
	// signal that lasts more than "2^16-1*Res" duration by skipping a pulse.
	Edges []uint16
	// Res is the minimum resolution at which the edges should be
	// rasterized.
	//
	// The lower the value, the more memory shall be used when rasterized.
	Freq physic.Frequency
}

// Frequency implements Stream.
func (e *EdgeStream) Frequency() physic.Frequency {
	return e.Freq
}

// Duration implements Stream.
func (e *EdgeStream) Duration() time.Duration {
	if e.Freq == 0 {
		return 0
	}
	t := 0
	for _, edge := range e.Edges {
		t += int(edge)
	}
	return e.Freq.Period() * time.Duration(t)
}

// Program is a loop of streams.
//
// This is itself a stream, it can be used to reduce memory usage when repeated
// patterns are used.
type Program struct {
	Parts []Stream // Each part must be a BitStream, EdgeStream or Program
	Loops int      // Set to -1 to create an infinite loop
}

// Frequency implements Stream.
func (p *Program) Frequency() physic.Frequency {
	if p.Loops == 0 {
		return 0
	}
	var buf [16]physic.Frequency
	freqs := buf[:0]
	for _, part := range p.Parts {
		if f := part.Frequency(); f != 0 {
			freqs = insertFreq(freqs, f)
		}
	}
	if len(freqs) == 0 {
		return 0
	}
	f := freqs[0]
	for i := 1; i < len(freqs); i++ {
		if r := freqs[i]; r*2 < f {
			break
		}
		// Take in account Nyquist rate. https://wikipedia.org/wiki/Nyquist_rate
		f *= 2
	}
	return f
}

// Duration implements Stream.
func (p *Program) Duration() time.Duration {
	if p.Loops == 0 {
		return 0
	}
	var d time.Duration
	for _, s := range p.Parts {
		d += s.Duration()
	}
	if p.Loops > 1 {
		d *= time.Duration(p.Loops)
	}
	return d
}

//

// PinIn allows to read a bit stream from a pin.
//
// Caveat
//
// This interface doesn't enable sampling multiple pins in a
// synchronized way or reading in a continuous uninterrupted way. As such, it
// should be considered experimental.
type PinIn interface {
	pin.Pin
	// StreamIn reads for the pin at the specified resolution to fill the
	// provided buffer.
	//
	// May only support a subset of the structs implementing Stream.
	StreamIn(p gpio.Pull, b Stream) error
}

// PinOut allows to stream to a pin.
//
// The Stream may be a Program, a BitStream or an EdgeStream. If it is a
// Program that is an infinite loop, a separate goroutine can be used to cancel
// the program. In this case StreamOut() returns without an error.
//
// Caveat
//
// This interface doesn't enable streaming to multiple pins in a
// synchronized way or reading in a continuous uninterrupted way. As such, it
// should be considered experimental.
type PinOut interface {
	pin.Pin
	StreamOut(s Stream) error
}

//

// insertFreq inserts in reverse order, highest frequency first.
func insertFreq(l []physic.Frequency, f physic.Frequency) []physic.Frequency {
	i := search(len(l), func(i int) bool { return l[i] < f })
	l = append(l, 0)
	copy(l[i+1:], l[i:])
	l[i] = f
	return l
}

// search implements the same algorithm as sort.Search().
//
// It was extracted to to not depend on sort, which depends on reflect.
func search(n int, f func(int) bool) int {
	lo := 0
	for hi := n; lo < hi; {
		if i := int(uint(lo+hi) >> 1); !f(i) {
			lo = i + 1
		} else {
			hi = i
		}
	}
	return lo
}

var _ Stream = &BitStream{}
var _ Stream = &EdgeStream{}
var _ Stream = &Program{}
