// Package pcd8544 implements a driver for the PCD8544 48x84 pixels matrix LCD, used in Nokia's 5110 and 3310 phones.
//
// Datasheet: http://eia.udg.edu/~forest/PCD8544_1.pdf
package pcd8544 // import "tinygo.org/x/drivers/pcd8544"

import (
	"errors"
	"image/color"
	"time"

	"tinygo.org/x/drivers"
	"tinygo.org/x/drivers/internal/pin"
)

// Device wraps an SPI connection.
type Device struct {
	bus        drivers.SPI
	dcPin      pin.OutputFunc
	rstPin     pin.OutputFunc
	scePin     pin.OutputFunc
	buffer     []byte
	width      int16
	height     int16
	bufferSize int16
}

type Config struct {
	Width  int16
	Height int16
}

// New creates a new PCD8544 connection. The SPI bus must already be configured.
func New(bus drivers.SPI, dcPin, rstPin, scePin pin.Output) *Device {
	return &Device{
		bus:    bus,
		dcPin:  dcPin.Set,
		rstPin: rstPin.Set,
		scePin: scePin.Set,
	}
}

// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
	if cfg.Width != 0 {
		d.width = cfg.Width
	} else {
		d.width = 84
	}
	if cfg.Height != 0 {
		d.height = cfg.Height
	} else {
		d.height = 48
	}
	d.bufferSize = d.width * d.height / 8
	d.buffer = make([]byte, d.bufferSize)

	d.rstPin.Low()
	time.Sleep(100 * time.Nanosecond)
	d.rstPin.High()
	d.SendCommand(FUNCTIONSET | EXTENDEDINSTRUCTION) // H = 1
	d.SendCommand(SETVOP | 0x3f)                     // 0x3f : Vop6 = 0, Vop5 to Vop0 = 1
	d.SendCommand(SETTEMP | 0x03)                    // Experimentally determined
	d.SendCommand(SETBIAS | 0x03)                    // Experimentally determined
	d.SendCommand(FUNCTIONSET)                       // H = 0
	d.SendCommand(DISPLAYCONTROL | DISPLAYNORMAL)
}

// ClearBuffer clears the image buffer
func (d *Device) ClearBuffer() {
	d.buffer = make([]byte, d.bufferSize)
}

// ClearDisplay clears the image buffer and clear the display
func (d *Device) ClearDisplay() {
	d.ClearBuffer()
	d.Display()
}

// Display sends the whole buffer to the screen
func (d *Device) Display() error {
	d.SendCommand(FUNCTIONSET) // H = 0
	d.SendCommand(SETXADDR)
	d.SendCommand(SETYADDR)

	for i := int16(0); i < d.bufferSize; i++ {
		d.SendData(d.buffer[i])
	}
	return nil
}

// sendDataCommand sends image data or a command to the screen
func (d *Device) sendDataCommand(isCommand bool, data uint8) {
	if isCommand {
		d.dcPin.Low()
	} else {
		d.dcPin.High()
	}
	d.scePin.Low()
	d.bus.Transfer(data)
	d.scePin.High()
}

// SetPixel enables or disables a pixel in the buffer
// color.RGBA{0, 0, 0, 255} is consider transparent, anything else
// with enable a pixel on the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
	if x < 0 || x >= d.width || y < 0 || y >= d.height {
		return
	}
	byteIndex := x + (y/8)*d.width
	if c.R != 0 || c.G != 0 || c.B != 0 {
		d.buffer[byteIndex] |= 1 << uint8(y%8)
	} else {
		d.buffer[byteIndex] &^= 1 << uint8(y%8)
	}
}

// GetPixel returns if the specified pixel is on (true) or off (false)
func (d *Device) GetPixel(x int16, y int16) bool {
	if x < 0 || x >= d.width || y < 0 || y >= d.height {
		return false
	}
	byteIndex := x + (y/8)*d.width
	return (d.buffer[byteIndex] >> uint8(y%8) & 0x1) == 1
}

// SetBuffer changes the whole buffer at once
func (d *Device) SetBuffer(buffer []byte) error {
	if int16(len(buffer)) != d.bufferSize {
		//return ErrBuffer
		return errors.New("wrong size buffer")
	}
	for i := int16(0); i < d.bufferSize; i++ {
		d.buffer[i] = buffer[i]
	}
	return nil
}

// SendCommand sends a command to the display
func (d *Device) SendCommand(command uint8) {
	d.sendDataCommand(true, command)
}

// SendData sends a data byte to the display
func (d *Device) SendData(data uint8) {
	d.sendDataCommand(false, data)
}

// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
	return d.width, d.height
}
