// Package ssd1289 implements a driver for the SSD1289 led matrix controller as packaged on the TFT_320QVT board
//
// Datasheet: http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf
package ssd1289

import (
	"image/color"
	"time"

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

type Bus interface {
	Set(data uint16)
}

type Device struct {
	rs  pin.OutputFunc
	wr  pin.OutputFunc
	cs  pin.OutputFunc
	rst pin.OutputFunc
	bus Bus
}

const width = int16(240)
const height = int16(320)

func New(rs, wr, cs, rst pin.Output, bus Bus) *Device {
	d := &Device{
		rs:  rs.Set,
		wr:  wr.Set,
		cs:  cs.Set,
		rst: rst.Set,
		bus: bus,
	}

	// configure GPIO pins (only on baremetal targets, for backwards compatibility)
	legacy.ConfigurePinOut(rs)
	legacy.ConfigurePinOut(wr)
	legacy.ConfigurePinOut(cs)
	legacy.ConfigurePinOut(rst)

	d.cs.High()
	d.rst.High()
	d.wr.High()

	return d
}

func (d *Device) lcdWriteCom(cmd Command) {
	d.rs.Low()
	d.lcdWriteBusInt(uint16(cmd))
}

func (d *Device) lcdWriteDataInt(data uint16) {
	d.rs.High()
	d.lcdWriteBusInt(data)
}

func (d *Device) lcdWriteComData(cmd Command, data uint16) {
	d.lcdWriteCom(cmd)
	d.lcdWriteDataInt(data)
}

func (d *Device) tx() {
	d.wr.Low()
	d.wr.High()
}

func (d *Device) lcdWriteBusInt(data uint16) {
	d.bus.Set(data)
	d.tx()
}

func (d *Device) Configure() {
	d.rst.High()
	time.Sleep(time.Millisecond * 5)
	d.rst.Low()
	time.Sleep(time.Millisecond * 15)
	d.rst.High()
	time.Sleep(time.Millisecond * 15)
	d.cs.Low()

	//Power supply setting
	d.lcdWriteComData(POWERCONTROL1, 0xA8A4)
	d.lcdWriteComData(POWERCONTROL2, 0x0000)
	d.lcdWriteComData(POWERCONTROL3, 0x080C)
	d.lcdWriteComData(POWERCONTROL4, 0x2B00)
	d.lcdWriteComData(POWERCONTROL5, 0x00B7)

	//Set R07h at 0021h
	d.lcdWriteComData(DISPLAYCONTROL, 0x021)

	//Set R00h at 0001h
	d.lcdWriteComData(OSCILLATIONSTART, 0x0001)

	//Set R07h at 0021h
	d.lcdWriteComData(DISPLAYCONTROL, 0x023)

	//Set R10h at 0000h, Exit sleep mode
	d.lcdWriteComData(SLEEPMODE, 0x0000)

	//Wait 30ms
	time.Sleep(time.Millisecond * 30)

	//Set R07h at 0033h
	d.lcdWriteComData(DISPLAYCONTROL, 0x033)

	//Entry Mode setting (R11h)
	//DFM   11 --> 65k
	//TRANS  0
	//OEDEF  0
	//WMODE  0 --> Normal data bus
	//DMODE 00 --> Ram
	//TY    01 --> 262k Type A not used as we are in 65k mode.
	//ID    11 --> Horizontal & Vertical increment
	//AM     0 --> Horizontal
	//LG   000 --> No compare register usage
	d.lcdWriteComData(ENTRYMODE, 0x6030)

	//LCD Driver AC Setting
	//I couldn't make sense of the documentation fortunately 0 seems to
	//FLD 0 --> Normal driving
	//ENWS 0 --> POR mode
	//BC   1 --> Less flicker
	//EOR  1 --> Less stripey
	//WSMD 0 --> not used in POR mode
	//NW   0 --> Least flicker
	d.lcdWriteComData(LCDDRIVEACCONTROL, 0x0600)

	//End of documented init

	//RL    0 --> Output shift direction
	//REV   1 --> Reverse colors
	//CAD   0 --> Cs on common
	//BGR   0 --> use RGB color assignment
	//SM    0 --> standard gate scan sequence
	//TB    1 --> Display is mirrored with 0
	//MUX 319 --> Number of lines in display
	d.lcdWriteComData(DRIVEROUTPUTCONTROL, 0x233F)

	d.cs.High()

}

func (d *Device) setXY(x1 uint16, y1 uint16, x2 uint16, y2 uint16) {
	d.lcdWriteComData(HORIZONTALRAMADDRESSPOSITION, (x2<<8)+x1)
	d.lcdWriteComData(VERTICALRAMADDRESSSTARTPOSITION, y1)
	d.lcdWriteComData(VERTICALRAMADDRESSENDPOSITION, y2)
	d.lcdWriteComData(SETGDDRAMXADDRESSCOUNTER, x1)
	d.lcdWriteComData(SETGDDRAMYADDRESSCOUNTER, y1)
	d.lcdWriteCom(RAMDATAREADWRITE)
}

func (d *Device) ClearDisplay() {
	d.FillDisplay(color.RGBA{0, 0, 0, 255})
}

func (d *Device) FillDisplay(c color.RGBA) {
	d.FillRect(0, 0, width, height, c)
}

func encodeColor(c color.RGBA) uint16 {
	encoded := (uint16(c.B)&248)<<8 | (uint16(c.G)&252)<<3 | (uint16(c.R)&248)>>3
	return encoded
}

func (d *Device) SetPixel(x, y int16, c color.RGBA) {

	encoded := encodeColor(c)

	d.cs.Low()
	d.setXY(uint16(x), uint16(y), uint16(x), uint16(y))
	d.rs.High()
	d.lcdWriteBusInt(encoded)
	d.cs.High()
}

func (d *Device) FillRect(x, y, w, h int16, c color.RGBA) {
	encoded := encodeColor(c)

	d.cs.Low()
	d.setXY(uint16(x), uint16(y), uint16(x+(w-1)), uint16(y+(h-1)))
	d.rs.High()
	d.bus.Set(encoded)
	for i := int64(0); i < int64(w)*int64(h); i++ {
		d.tx()
	}
	d.cs.High()
	d.rs.Low()

}

func (d *Device) Display() error {
	//Not enough memory to store an entire screen on most microcontrollers
	return nil
}

func (d *Device) Size() (x, y int16) {
	return width, height
}
