// 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 bcm283x

import (
	"encoding/binary"
	"errors"
	"fmt"

	"periph.io/x/conn/v3/gpio/gpiostream"
)

// uint32ToBitLSBF packs a bit offset found on slice `d` (that is actually
// uint32) back into a densely packed Bits stream.
func uint32ToBitLSBF(w []byte, d []uint8, bit uint8, skip int) {
	// Little endian.
	x := bit / 8
	d = d[x:]
	bit -= 8 * x
	mask := uint8(1) << bit
	for i := range w {
		w[i] = ((d[0]&mask)>>bit<<0 |
			(d[skip*1]&mask)>>bit<<1 |
			(d[skip*2]&mask)>>bit<<2 |
			(d[skip*3]&mask)>>bit<<3 |
			(d[skip*4]&mask)>>bit<<4 |
			(d[skip*5]&mask)>>bit<<5 |
			(d[skip*6]&mask)>>bit<<6 |
			(d[skip*7]&mask)>>bit<<7)
		d = d[skip*8:]
	}
}

func getBit(b byte, index int, msb bool) byte {
	var shift uint
	if msb {
		shift = uint(7 - index)
	} else {
		shift = uint(index)
	}
	return (b >> shift) & 1
}

func raster32Bits(s gpiostream.Stream, skip int, clear, set []uint32, mask uint32) error {
	var msb bool
	var bits []byte
	switch b := s.(type) {
	case *gpiostream.BitStream:
		msb = !b.LSBF
		bits = b.Bits
	default:
		return fmt.Errorf("unsupported type %T", b)
	}
	m := len(clear) / 8
	if n := len(bits); n < m {
		m = n
	}
	index := 0
	for i := 0; i < m; i++ {
		for j := 0; j < 8; j++ {
			if getBit(bits[i], j, msb) != 0 {
				for k := 0; k < skip; k++ {
					set[index] |= mask
					index++
				}
			} else {
				for k := 0; k < skip; k++ {
					clear[index] |= mask
					index++
				}
			}
		}
	}
	return nil
}

// raster32 rasters the stream into a uint32 stream with the specified masks to
// put in the correctly slice when the bit is set and when it is clear.
//
// `s` must be one of the types in this package.
func raster32(s gpiostream.Stream, skip int, clear, set []uint32, mask uint32) error {
	if mask == 0 {
		return errors.New("bcm283x: mask is 0")
	}
	if len(clear) == 0 {
		return errors.New("bcm283x: clear buffer is empty")
	}
	if len(set) == 0 {
		return errors.New("bcm283x: set buffer is empty")
	}
	if len(clear) != len(set) {
		return errors.New("bcm283x: clear and set buffers have different length")
	}
	switch x := s.(type) {
	case *gpiostream.BitStream:
		// TODO
		return raster32Bits(x, skip, clear, set, mask)
	case *gpiostream.EdgeStream:
		return errors.New("bcm283x: EdgeStream is not supported yet")
	case *gpiostream.Program:
		return errors.New("bcm283x: Program is not supported yet")
	default:
		return errors.New("bcm283x: unknown stream type")
	}
}

// PCM/PWM DMA buf is encoded as little-endian and MSB first.
func copyStreamToDMABuf(w gpiostream.Stream, dst []uint32) error {
	switch v := w.(type) {
	case *gpiostream.BitStream:
		if v.LSBF {
			return errors.New("TODO(simokawa): handle BitStream.LSBF")
		}
		// This is big-endian and MSB first.
		i := 0
		for ; i < len(v.Bits)/4; i++ {
			dst[i] = binary.BigEndian.Uint32(v.Bits[i*4:])
		}
		last := uint32(0)
		if mod := len(v.Bits) % 4; mod > 0 {
			for j := 0; j < mod; j++ {
				last |= (uint32(v.Bits[i*4+j])) << uint32(8*(3-j))
			}
			dst[i] = last
		}
		return nil
	case *gpiostream.EdgeStream:
		return errors.New("TODO(simokawa): handle EdgeStream")
	default:
		return errors.New("unsupported Stream type")
	}
}
